In Ruby, what does a `while` loop, an `each` call or a `map` call return when `break` ends it early, and when it completes?
answer
- break's argument replaces the normal result
- bare break gives nil
- completed map gives the new array
- completed each returns its receiver
- not-found each returns a truthy array
basics
~20 sA break ends a Ruby loop or iterator call with break's argument, or nil for a bare break. Run to completion, while and until give nil, each and times give their receiver, and map gives the new array.
solid answer
~40 s`break value` makes the interrupted construct evaluate to `value`, and a bare `break` makes it `nil`. That replaces the normal result entirely. When nothing breaks, the constructs differ: `while`/`until` evaluate to `nil`, `for` and `each` return the collection, `Integer#times` returns the integer, `map` returns the new array, and `loop` — which only ends by `break` or `StopIteration` — has no completed case. The classic bug is `bin = bins.each { |b| break b if b.empty? }`: when no empty bin exists, `each` completes and returns `bins`, a truthy array, so `if bin` passes. Use `find`, which returns `nil` on a miss, or end the search with an explicit `nil`. Likewise, a `break` inside `map` returns the break value, not the partial array built so far.
code
ruby · 8 linesbins = [[:bolt], [:nut]]
hit = bins.each { |b| break b if b.empty? }
p hit # => [[:bolt], [:nut]] (truthy!)
p bins.find(&:empty?) # => nil
p [1, 2, 3].map { |v| break :halt if v == 2; v } # => :haltgo deeper
Recall that break can carry a value, a bare break gives nil, and a completed while is nil while a completed each returns its receiver.
Explain the full table, including map returning the break value rather than a partial array, and why each's receiver makes not-found checks wrong.
Spot found-or-not bugs that rely on a loop's value in review and replace them with find, any? or an explicit nil miss value.
Push teams toward methods with a defined miss value so that search results never depend on how a loop happened to end.
## Every loop is an expression In Ruby, loops and iterator calls are expressions: they evaluate to a value you can assign. What that value is depends on **how the loop ended**: 1. **It completed** — the condition became false or the collection ran out. 2. **It was ended by `break`** — the value is `break`'s argument, or `nil` if there was none. `break`'s value replaces the normal result entirely; nothing of the completed-case value survives. ## The table to memorise | Construct | Completed | `break` | `break value` | |---|---|---|---| | `while` / `until` | `nil` | `nil` | `value` | | `for x in list` | `list` | `nil` | `value` | | `list.each { }` | `list` (the receiver) | `nil` | `value` | | `n.times { }` | `n` | `nil` | `value` | | `list.map { }` | new array | `nil` | `value` | | `loop { }` | never completes; `StopIteration#result` if that ends it | `nil` | `value` | For iterator methods, "`break value` returns `value`" is a rule about **the method call**: `break` in a block terminates the method that yielded and that call evaluates to the break value. It holds for `each`, `map`, `times`, `each_with_index` and your own methods that `yield`. ```ruby i = 0 r = while i < 5 i += 1 break i * 10 if i == 3 end r # => 30 [3, 5, 7].each { |v| break v if v.even? } # => [3, 5, 7] [1, 2, 3].map { |v| break :halt if v == 2; v } # => :halt ``` ## The not-found trap Consider a warehouse routine that looks for the first empty bin: ```ruby bin = bins.each { |b| break b if b.empty? } restock(bin) if bin ``` When an empty bin exists, `break b` makes `each` return that bin. When none exists, `each` **completes** and returns `bins` itself — an array, which is truthy. `restock` is then called with the whole array. Two fixes: - Use the method designed for the job: `bins.find(&:empty?)` returns the element or `nil`. - If you need a hand-written loop, keep the answer in a variable that starts as `nil`, so the miss has a defined value: ```ruby bin = nil bins.each do |b| if b.empty? bin = b break end end ``` A method that returns the bin from inside the loop and `nil` after it is an equally clear alternative. ## The partial-result trap in `map` ```ruby labels = bins.map do |b| break if b.damaged? b.label end ``` People expect `labels` to hold the labels collected before the damaged bin. It does not: `break` replaces `map`'s return value, so `labels` is `nil`. `map` only returns an array when it completes. To collect up to a condition, use `take_while` and then `map`, or build the array yourself and `break` out of an `each`. ## `while` versus `each` - A **completed** `while` is `nil`, so `result = while ... end` is only useful when a `break` supplies the answer. - A **completed** `each` is the receiver, which is why `each` chains (`list.each { ... }.size`) and why using its value as "found or not" is dangerous. - A **bare** `break` gives `nil` in every construct. If the caller must tell "stopped early" from "finished", pass a value. ## Why the rules look like this The results are consistent once you see what each construct is for. `while` and `until` exist to repeat until a condition changes, so there is no natural value to hand back and Ruby gives `nil`. `each` exists to walk a collection for side effects, and returning the receiver lets calls chain. `map` exists to build a new array, so it returns that array. `break` is how the loop body chooses the loop's own value from inside, so its argument wins over every one of those defaults. The mistakes come from assuming one construct behaves like another: that `each` is `nil` like `while`, or that `map` keeps its partial work like a hand-built accumulator. ## Checklist when reviewing code that uses a loop's value - Which construct is it, and what does it give when it completes? - Does every `break` pass the value the caller expects? - Is the not-found case distinguishable from the found case? - Would `find`, `detect`, `any?`, `take_while` or `index` say the same thing with a defined miss value?
- In Ruby, what does a break inside the block of your own yielding method make that method return?The method call evaluates to the break value (or `nil` for a bare `break`), whatever the method would have returned. Code after the `yield` is skipped, but the method's `ensure` clauses run. The caller of the method sees only the break value.
- Why does a Ruby for loop broken with a bare break return nil when a completed one returns the collection?`for` is built on `each`, so it shares `each`'s results: completed, it evaluates to the collection it iterated; ended by `break`, it evaluates to break's argument, and a bare `break` has the argument `nil`.
saying these in an interview costs you the question
- A completed each returns nil, so it is safe to use as a found-or-not flag
- break inside map returns the elements mapped so far
- A bare break makes each return its receiver
- A break value is appended to map's result as one more element
- break's value is only available in while loops, not in iterator blocks