skip to content

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%

answer

  1. restart this pass, not the next
  2. no condition re-check
  3. same block argument again
  4. takes no value
  5. retry belongs to rescue

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.

solid answer

~40 s

`redo` jumps back to the start of the current pass. In a `while` or `until` loop it skips the condition check; in a block it re-runs the block with the **same** arguments, so `each` does not move on to the next element. It takes no argument, unlike `break` and `next`. Because nothing advances, a `redo` is only safe when the body changes some state that eventually stops it — a counter, a fresh read — otherwise the loop never ends. It is rare in application code; interviews use it to check that you can distinguish it from `next` (move on to the next pass) and from `retry`, which is valid only inside a `rescue` clause and re-runs the `begin` block.

code

ruby · 7 lines
ruby
tries = Hash.new(0)
scans = [:a, :b]
scans.each do |bin|
  tries[bin] += 1
  redo if bin == :b && tries[bin] < 3
end
p tries   # => {a: 1, b: 3}

go deeper

for a junior

Recall that redo repeats the current pass with the same element and does not check the loop condition again.

for a middle

Explain the difference between redo, next and retry and why redo needs state that changes inside the body to terminate.

for a senior

Treat an unbounded redo as a hang waiting to happen and replace it with a counted inner loop that raises when attempts run out.

for a principal

Accept redo only where it clearly beats an inner loop, and require a bound in review whenever it retries external input.

## What `redo` does `redo` is the least used of Ruby's loop keywords. It restarts the **current** pass of a loop or the **current** call of a block from its first statement: - In a **`while`/`until`** loop, control jumps to the top of the body. The condition is **not** tested again. - In a **block** (`each`, `map`, `times`, your own `yield`), the block runs again with the **same arguments**. The iterating method does not advance to the next element. Unlike `break` and `next`, `redo` takes **no argument**; `redo 1` is a syntax error. Like them, it must sit inside a loop or block: the parser reports `Invalid redo` anywhere else. ## An example from the language reference The syntax guide shows `redo` in a `while` loop: ```ruby result = [] while result.length < 10 do result << result.length redo if result.last.even? result << result.length + 1 end p result # => [0, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11] ``` Each `redo` skips the second `<<` and the length check, so the loop can overshoot the limit it looked like it had: the final array has eleven elements even though the condition says "less than 10". ## The same element again In a block, "the current pass" means "the current element": ```ruby attempts = 0 [:bin_a].each do |bin| attempts += 1 redo if attempts < 3 end attempts # => 3 ``` `each` yielded `:bin_a` once; `redo` made the block run three times with that same `:bin_a`. ## Why it can spin forever Nothing about `redo` moves the loop forward. Compare the three keywords: | Keyword | Next thing that runs | Condition re-tested? | Element advances? | |---|---|---|---| | `next` | the following pass | yes, in `while`/`until` | yes, in a block | | `redo` | the same pass again | no | no | | `break` | code after the loop | not applicable | not applicable | So a `redo` guarded by a condition that the body never changes repeats forever. Typical mistakes: 1. Guarding `redo` with a value computed before the loop, which the body never updates. 2. Expecting the `while` condition to stop a `redo` loop — it is not evaluated on a redo. 3. Retrying a flaky operation with `redo` and no attempt count, so a permanent failure never exits. A safe `redo` changes state first — a counter, or a freshly read value — and guards on that state. ## `redo` is not `retry` Older Ruby allowed `retry` in loops with a meaning close to `redo`. That is gone: the language reference notes that `retry` outside a `rescue` clause is now a `SyntaxError`. Today: - **`redo`** repeats the current iteration of a loop or block. - **`retry`** appears in a `rescue` clause and re-runs the `begin` block (or method body) that raised; it is an exception-handling tool. ## A bounded re-prompt Here is the one shape where `redo` reads naturally — asking again for the same item — written with the bound that keeps it safe: ```ruby attempts = Hash.new(0) bins.each do |bin| attempts[bin] += 1 count = scanner.count(bin) if count.nil? raise "scanner failed on #{bin}" if attempts[bin] >= 3 redo end record(bin, count) end ``` The counter is updated before the guard, the guard raises when the attempts run out, and `redo` keeps the loop on the same `bin` until a count arrives. Without the `raise`, a scanner that never recovers would stall the whole walk on that bin. ## When to use it - **Rarely.** Most code that looks like it needs `redo` reads better as an inner loop: `until valid; value = read; end` inside the outer iteration. - **Re-prompting for the same item** — asking again for input about the same record until it is valid — is the classic legitimate use. - **Always bound it.** Count the attempts and give up with an error, or the program can hang on bad input.

  • In a Ruby while loop, can redo take the loop past the limit written in its condition?
    Yes. `redo` restarts the body without evaluating the condition, so any code in the body runs again even if the condition would now be false. The syntax guide's example ends with eleven elements in an array whose loop condition reads `result.length < 10`.
  • Why is redo 1 rejected by Ruby when break 1 and next 1 are accepted?
    `redo` has no value to deliver: it neither ends the loop, whose value `break` supplies, nor ends the block call, whose value `next` supplies. It only restarts the current pass, so the grammar gives it no argument and `redo 1` is a syntax error.

saying these in an interview costs you the question

  • redo moves on to the next element, like next
  • redo re-checks the while condition before restarting
  • redo accepts a value that becomes the iteration's result
  • retry can still be used in a plain loop to restart an iteration
  • The while condition will eventually stop a redo loop on its own