skip to content

Looping Constructs

Ruby has while, until, for and loop, plus Integer#times and upto for counting. Interviewers check that you know for leaks its variable and why idiomatic code prefers each and friends.

on this pageshow

explore

questions

5

In Ruby, why is `for x in list` discouraged in favour of `list.each`, and what does the for loop leave behind?

level: juniorimportance: must knowfreq 68%

answer

  1. no new variable scope
  2. loop variable outlives the loop
  3. one shared variable for every closure
  4. for calls each under the hood
  5. RuboCop Style/For defaults to each

basics

~20 s

Ruby's for...in calls each on the collection but opens no new scope, so its loop variable and body locals survive the loop and every closure made inside shares one variable; each gives every iteration a fresh block parameter.

solid answer

~40 s

`for item in list` is syntax that calls `list.each`, but unlike a block it does not create a new variable scope. The loop variable is an ordinary local of the surrounding method: after the loop it still holds the last element, or `nil` if the collection was empty, and any local first assigned in the body survives too. Lambdas created in the body all close over that one variable, so `for i in 1..3; procs << -> { i }; end` makes every proc return `3`, while `(1..3).each { |i| procs << -> { i } }` gives each iteration its own `i`. A `for` loop returns the collection, the same as `each`. Idiomatic Ruby writes `each` (or a more specific Enumerable method), and RuboCop's `Style/For` cop enforces `each` by default.

code

ruby · 11 lines
ruby
for zone in [:north, :south]
  last_seen = zone
end
p zone       # => :south
p last_seen  # => :south

[:east, :west].each do |side|
  first_seen = side
end
p defined?(side)        # => nil
p defined?(first_seen)  # => nil

go deeper

for a junior

Recall that for calls each, returns the collection, and leaves its loop variable defined after the loop; say that idiomatic Ruby writes each.

for a middle

Explain that the difference is scope: blocks open one and for does not, which is why lambdas made in a for body all see the final value.

for a senior

Point to real bugs the leak causes, such as callbacks bound to the last element or a temporary shadowing a later name, and why the Style/For autocorrect is unsafe.

for a principal

Frame for as a readability and consistency decision: one iteration idiom across a codebase lets reviewers and tooling reason about scope without special cases.

## What `for...in` actually is Ruby has a `for` loop that looks familiar to people arriving from other languages: ```ruby for sensor in sensors puts sensor.name end ``` It is not a separate iteration engine. The language reference describes it as `for`, a variable, `in`, and "the value to iterate over using `#each`". In other words, `for` asks the collection for its `each` method and runs the body once per yielded value. Anything that responds to `each` works: arrays, hashes, ranges, or your own class. An object without `each` fails with a `NoMethodError` for `each`. So the question is never "is `for` faster or more powerful than `each`?" Both end up in the same `each` call. The difference is **scope**. ## The scope difference A block (`do ... end` or `{ ... }`) opens a new local-variable scope. Its parameters, and any variable first assigned inside it, exist only inside that block. A `for` loop opens **no** scope: the loop variable and the body's locals belong to the enclosing method or script. | Aspect | `for x in list` | `list.each { \|x\| ... }` | |---|---|---| | New variable scope | no | yes | | `x` after the loop | last element (or `nil` when the list was empty) | undefined unless it existed before | | Locals first set in the body | still visible after the loop | gone after the block | | Closures made in the body | share one `x` | each iteration has its own `x` | | Return value | the collection | the collection | | Destructuring | `for k, v in hash` | `hash.each { \|k, v\| ... }` | Two consequences follow: - **The variable leaks.** After `for x in [1, 2, 3]; end`, `x` is `3`. After a loop over an empty array it is `nil`: the parser has seen the assignment, so the local exists, but nothing was ever assigned to it. - **Body locals leak.** A temporary such as `reading = sensor.read` inside a `for` body is still readable after the loop and can silently shadow a later use of the same name. ## The closure trap The leak matters most when the body creates a closure. Consider a greenhouse controller building one callback per sensor slot: ```ruby callbacks = [] for slot in 1..3 callbacks << -> { "slot #{slot}" } end callbacks.map(&:call) # => ["slot 3", "slot 3", "slot 3"] callbacks = [] (1..3).each { |slot| callbacks << -> { "slot #{slot}" } } callbacks.map(&:call) # => ["slot 1", "slot 2", "slot 3"] ``` With `for` there is exactly one `slot` variable, reassigned on every pass, and each lambda captures that variable rather than a snapshot of its value. By the time the lambdas run, it holds the last value. With `each`, every call of the block gets a fresh `slot` parameter, so each lambda keeps its own. ## What stays the same It is worth being precise, because weak answers overstate the difference: 1. Both forms call `each` on the collection, so both need an object that responds to `each`. 2. Both return the collection itself when they run to completion. 3. Both can be left early with the jump keywords, which have their own rules. 4. Both can destructure pairs: `for key, value in hash` works just as `hash.each { |key, value| ... }` does. The difference is only where the variables live. ## Why idiomatic Ruby prefers `each` - **No leaked state.** Variables stay inside the block, which keeps methods short-lived and avoids accidental reuse of a name. - **Closures behave as readers expect.** Each iteration captures its own values. - **One style for everything.** Once you write `each`, switching to `map`, `select`, `each_with_index` or `each_slice` is a one-word change; a `for` loop has to be rewritten. - **Tooling agrees.** RuboCop's `Style/For` cop has `EnforcedStyle: each` in its default configuration, and its autocorrection is marked unsafe precisely because turning `for` into `each` changes variable scope. - **Readers expect it.** The language reference itself notes that `for` is rarely used in modern Ruby programs. The practical interview answer: `for` works, but it is `each` with the scope removed, and the removed scope is a source of bugs rather than a feature.

  • What does the loop variable hold after a Ruby for loop over an empty array?
    It holds `nil`. The parser has already seen the assignment in `for x in []`, so `x` exists as a local of the enclosing scope, but the body never ran and nothing was assigned to it. `defined?(x)` returns `"local-variable"`, not `nil`.
  • Does a Ruby for loop work on an object that has no each method?
    No. `for` iterates by calling `each` on the object after `in`, so a class without `each` raises `NoMethodError` for `each`. Defining `each` on your own class is enough to make it usable in a `for` loop and in a block-based `each` call.
  • If for and each both call each, why does RuboCop mark its Style/For autocorrection unsafe?
    Because the rewrite changes scope. Code after a `for` loop may read the loop variable or a body local that leaked out; after the same body becomes an `each` block, those variables no longer exist there, so the corrected program can raise `NameError` or read a different value.

saying these in an interview costs you the question

  • for creates its own scope, just like a block does
  • The loop variable is undefined once a for loop ends
  • Each closure created in a for loop captures its own copy of the variable
  • for iterates by index, so it works on objects without an each method
  • A for loop returns nil, unlike each which returns the receiver
open as a page

In Ruby, how do Integer#times, upto and downto replace a counting loop, and what does each call return?

level: juniorimportance: should knowfreq 55%

basics

~10 s

Ruby counts with methods on Integer: n.times yields 0 to n-1, a.upto(b) and a.downto(b) yield every integer between the two, inclusive. With a block each returns its receiver; without one each returns an Enumerator.

open as a page

In Ruby, how does `begin ... end while cond` differ from the `stmt while cond` modifier, and why does RuboCop's Lint/Loop flag it?

level: middleimportance: should knowfreq 38%

basics

~20 s

A while or until modifier on an ordinary statement tests first, so the statement may never run; on a begin...end block it tests after, so the body runs at least once. Lint/Loop flags that special case and suggests loop with break.

open as a page

A greenhouse controller's `loop do` that polls a flaky sensor stops with no error and no log line — how can Ruby's Kernel#loop cause that?

level: seniorimportance: should knowfreq 26%

basics

~10 s

Kernel#loop rescues StopIteration raised anywhere in its block and returns that exception's result, so an exhausted enumerator deep in the polling code ends the loop as if it had finished normally.

open as a page

In Ruby, why does `1.0.step(2.0, 0.1)` end exactly at 2.0 when a `while` loop adding 0.1 each pass misses it?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

For floats, Numeric#step computes the number of values up front and yields start + i * step, clamping an overshoot to the limit, so rounding error never accumulates; a while loop that keeps adding 0.1 drifts and skips 2.0.

open as a page