skip to content

Break, Next & Catch

break and next carry values out of loops and blocks, redo repeats an iteration, and catch/throw unwinds across method calls without an exception. Interviewers test what a broken loop returns.

on this pageshow

explore

questions

5

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
open as a page

In Ruby, what does a `while` loop, an `each` call or a `map` call return when `break` ends it early, and when it completes?

level: middleimportance: must knowfreq 48%

basics

~20 s

A 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.

open as a page

In Ruby, when nested `each` blocks search a warehouse's aisles and bins, how do you stop every level at the first matching bin?

level: middleimportance: should knowfreq 40%

basics

~20 s

A Ruby break ends only the innermost each, so outer levels keep going. Wrap the loops in catch(:found) and throw :found, bin, extract them into a method that returns the bin, or rewrite the search as a single find.

open as a page

In Ruby, how do `catch`/`throw` differ from `raise`/`rescue`, and what happens when `throw` finds no matching `catch`?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Ruby's throw is non-error unwinding to the catch with the same tag object, handing it a value and running ensure clauses; rescue never sees it. With no matching catch, throw raises UncaughtThrowError, an ArgumentError subclass.

open as a page

In Ruby, what does `redo` do inside a `while` loop or an iterator block, and why can it spin forever?

level: middleimportance: nice to knowfreq 14%

basics

~10 s

Ruby's redo restarts the current iteration from the top of the body without re-testing the loop condition or advancing to the next element; if whatever triggered it never changes, the same pass repeats forever.

open as a page