skip to content

In Ruby, what is the difference between `break` and `next` inside an `each` or `map` block, and what value can `next` carry?

level: juniorimportance: must knowfreq 62%

answer

  1. one iteration versus the whole call
  2. next acts as the block's return
  3. next value becomes the map element
  4. bare next yields nil
  5. break ends the method that yielded

basics

~20 s

In a Ruby block, next ends only the current call of the block and its argument (nil by default) becomes the block's result; break ends the whole iteration and makes the method that yielded, such as each or map, return immediately.

solid answer

~40 s

`next` behaves like `return` for the block: it stops the current iteration, and whatever you pass it becomes the value the block produced for that element. In `map`, `next 0 if bad` puts `0` in that slot and a bare `next` puts `nil` there; `next` never removes the element. `break` stops iterating altogether: it terminates the method that yielded to the block — `each`, `map`, or your own method that calls `yield` — and that call returns immediately. In a `while` or `until` loop, `next` jumps back to the condition check and `break` leaves the loop. Using either keyword in a method body outside any loop or block is a `SyntaxError` ("Invalid break", "Invalid next").

code

ruby · 9 lines
ruby
weights = [4, 0, 7]
p weights.map { |w| next :empty if w.zero?; w * 2 }
# => [8, :empty, 14]

total = weights.reduce(0) do |sum, w|
  next sum if w.zero?   # keep the accumulator
  sum + w
end
p total  # => 11

go deeper

for a junior

Recall that next skips to the following element and break stops iterating, and that next's argument becomes that element's block value.

for a middle

Explain that next is the block's return while break terminates the yielding method, and show the reduce accumulator trap.

for a senior

Use break and next deliberately in methods that yield, knowing ensure still runs and code after yield does not when a caller breaks.

for a principal

Prefer expressive Enumerable methods over dense next and break logic in shared code so reviewers can see intent at a glance.

## Two keywords, two different reaches Both keywords stop the code that follows them, but they stop different things. - **`next`** ends the **current call of the block** (or the current pass of a `while`/`until` loop). Iteration carries on with the following element. - **`break`** ends the **whole iteration**. In a block, it terminates the method that yielded to the block; in a `while`/`until` loop, it leaves the loop. ```ruby [1, 2, 3, 4].each do |bin| next if bin.even? # skip even bins, keep going break if bin > 2 # stop scanning entirely puts bin end # prints 1 ``` ## `next` is the block's `return` A block is a piece of code that a method calls with `yield`. When the block finishes, its last expression becomes the value of that `yield`. `next` lets you finish early and choose that value: ```ruby [1, 2, 3].map { |v| next 0 if v == 2; v * 10 } # => [10, 0, 30] [1, 2, 3].map { |v| next if v == 2; v * 10 } # => [10, nil, 30] ``` Three consequences matter in practice: 1. **`next` does not delete elements.** `map` always returns one element per input; a bare `next` contributes `nil`. To drop elements you need a different method, not a different keyword. 2. **The argument is the iteration's result.** Whatever method is consuming the block's value sees it: `map` stores it, `select` tests its truthiness, `sort_by` uses it as a key. 3. **An accumulator can be lost.** In `reduce`, the block's value becomes the next accumulator. `next if v.odd?` returns `nil`, so the following pass receives `nil` as the running total and `nil + v` raises `NoMethodError`. Write `next sum if v.odd?`. RuboCop's `Lint/NextWithoutAccumulator` catches exactly this. ## `break` ends the method that yielded `break` in a block does not just leave the block; it unwinds out of the method that called `yield` and makes that **method call** finish. The method's remaining code after the `yield` does not run, though its `ensure` clauses do: ```ruby def scan_bins yield :a puts "after yield" # skipped by break :done ensure puts "cleanup" # still runs end scan_bins { break } # prints "cleanup" scan_bins { next } # prints "after yield" and "cleanup" ``` The value the interrupted call evaluates to is a separate topic (it is whatever `break` was given), and it differs from what the call returns when it completes. ## In `while` and `until` loops | Keyword | In a block | In `while` / `until` | |---|---|---| | `next` | ends this call of the block; its argument is the block's value | jumps to the condition check for the next pass | | `next value` | `value` becomes the block's result | the value is discarded | | `break` | ends the method that yielded | leaves the loop | | outside any loop or block | `SyntaxError` | `SyntaxError` | Note that `next` in a `while` loop re-evaluates the condition. If the code that moves the loop forward sits after the `next`, skipping it can make the loop spin forever. ## Where they are not allowed `break`, `next` and `redo` need an enclosing loop or block. The parser rejects them elsewhere: `def m; break; end` fails to compile with `Invalid break`, and the same holds for `next`. A method that wants to stop early uses `return`. ## Common wrong answers - "`next` removes the element from `map`." It supplies `nil` or its argument instead; the array keeps its length. - "`break` only leaves the block." It ends the method that called the block, so `each` does not continue with later elements. - "`next` in a `while` loop restarts the body." It goes back to the condition, which decides whether there is another pass; restarting the body without the test is `redo`. - "`break` skips cleanup." `ensure` clauses in the method being ended still run. Getting these right is what separates "I use them" from "I know what they do" in a screen. ## Summary for an interview - `next` = finish this iteration; its argument (default `nil`) is what the block produced. - `break` = stop iterating; the yielding method stops and returns straight away. - In `map`, `next` changes an element; it never removes one. - In `reduce`, a bare `next` hands `nil` to the next pass as the accumulator.

  • In Ruby, why does [1, 2, 3, 4].reduce(0) { |s, v| next if v.odd?; s + v } raise NoMethodError?
    Because the block's value becomes the next accumulator, and a bare `next` makes that value `nil`. On the pass after an odd number, `s` is `nil` and `nil + v` fails. Write `next s if v.odd?`; RuboCop's `Lint/NextWithoutAccumulator` flags the bare form inside `reduce` and `inject`.
  • If a method of yours yields to a block and the block calls break, does code after your yield still run?
    No. `break` terminates the method that yielded, so the statements after `yield` are skipped and the call returns at once. `ensure` clauses in that method do still run, which is where cleanup belongs.

saying these in an interview costs you the question

  • next inside map removes that element from the result
  • break in a block only leaves the block, and the method keeps iterating
  • next with a value is ignored inside a block
  • break inside a block skips the ensure clauses of the yielding method
  • next in a while loop jumps to the top of the body without testing the condition