skip to content

Each-Style Iterators

each, each_with_index, each_with_object, each_slice and each_cons walk a collection for side effects and return the receiver. Interviewers check you pick the right one over an index counter.

on this pageshow

explore

questions

5

In Ruby, what does Array#each return when called with a block, and why does assigning that result not collect the block's values?

level: juniorimportance: must knowfreq 72%

answer

  1. iteration for side effects
  2. block's value is thrown away
  3. returns self, the receiver
  4. same object: equal? is true
  5. collecting is map's job

basics

~20 s

Array#each with a block returns the receiver itself, the very same array object, and throws away whatever the block returns. Assigning its result gives you the original array back, never the block's values; map is the method that collects them.

solid answer

~40 s

`each` is Ruby's side-effect iterator: it yields every element to the block and, when it finishes, returns **`self`**, the receiver. The block's return value is discarded on every call. So `result = finishers.each { |r| r.upcase }` leaves `result` pointing at the same `Array` as `finishers` (`result.equal?(finishers)` is `true`), not at an array of upcased names; that is what `map` is for. `Hash#each` returns the hash and `Range#each` returns the range, following the same rule. Returning the receiver is deliberate: a method whose last line is `rows.each { ... }` hands the collection back to its caller, and you can keep chaining on it. The trap is aliasing: because it is the same object, pushing onto `result` changes `finishers` too.

code

ruby · 13 lines
ruby
finishers = ["Abebe", "Chen", "Okafor"]

result = finishers.each { |name| "#{name} finished" }
result                     # => ["Abebe", "Chen", "Okafor"]
result.equal?(finishers)   # => true, the same Array object

result << "Silva"
finishers                  # => ["Abebe", "Chen", "Okafor", "Silva"]

labels = finishers.map { |name| "#{name} finished" }
labels.first               # => "Abebe finished"

{101 => 7530}.each { |bib, secs| secs * 2 }  # => {101 => 7530}

go deeper

for a junior

Recall that each returns the receiver and ignores what the block returns, and that map is the method that collects block results.

for a middle

Explain that the returned object is the same array, not a copy, so mutating the result mutates the original, and that each re-reads the length while iterating.

for a senior

Spot methods that return the result of a trailing each by accident, and treat mutation during Array#each as a review finding rather than a style nit.

for a principal

Frame iteration methods by what they return: side-effect walkers return the receiver, builders return new objects, and a codebase stays readable when each is used only for side effects.

## What `each` is for In Ruby, **`each`** is the basic iterator that every core collection defines. `Array#each`, `Hash#each` and `Range#each` walk their elements in order and pass each one to the **block**, the `{ ... }` or `do ... end` chunk of code attached to the call. `each` exists for **side effects**: printing a line, writing a row, sending a message. It is the method the `Enumerable` module builds everything else on top of. The question interviewers ask is not how to call it but what comes back when it finishes. ## What it returns With a block, `each` returns **`self`**, the object you called it on. The array's rdoc in the Ruby source states the signature as `each {|element| ... } -> self`, and the C implementation ends with `return ary`. - `Array#each` returns the array. - `Hash#each` (an alias of `each_pair`) returns the hash. - `Range#each` returns the range. - Called **without** a block, `each` returns an `Enumerator` instead; that is a separate topic. The block's own return value is **discarded** on every iteration. Ruby evaluates it and moves on. ## The classic bug A marathon results page needs labels for each finisher. A newcomer writes: ```ruby labels = finishers.each { |name| "#{name} finished" } ``` and expects an array of strings. What they get is `finishers` itself. Three things are now true: 1. `labels == finishers` is `true`, because they hold the same names. 2. `labels.equal?(finishers)` is also `true`: it is the **same object**, not a copy. 3. `labels << "extra"` silently adds a row to `finishers`. The fix is to use the method whose job is to **collect** block results, `map`. `each` is only the right tool when nothing needs to be gathered, or when you gather explicitly into another object. ## Why returning `self` is useful Returning the receiver is not an accident; it makes `each` compose well: - A method whose last expression is `rows.each { |r| puts r }` returns `rows`, so the caller can keep working with the collection. - You can chain another call onto the end: `rows.each { |r| log(r) }.size`. - Most of the `each` family follows the same rule, so you can predict the return value without looking it up. | Method (with a block) | Returns | |---|---| | `each` | the receiver | | `each_with_index` | the receiver | | `reverse_each` | the receiver | | `each_slice(n)`, `each_cons(n)` | the receiver (since Ruby 3.1) | | `each_with_object(memo)` | the memo object | | `cycle` | `nil` | | `map` | a new array of block results | ## Modifying the array while iterating `Array#each` checks the array's current length on every step rather than taking a snapshot. The rdoc notes that the array may be modified during iteration. Two consequences follow: - **Appending** inside the block means the new elements are visited too, so `a.each { |x| a << x }` never ends. - **Deleting** inside the block shifts later elements left, so the element after a deleted one is skipped. If you must change the collection, iterate over a copy (`a.dup.each`) or use a method designed for in-place removal. ## Implicit returns make it visible Ruby methods return the value of their last expression, so the receiver rule leaks into method results whether you intend it or not: ```ruby def print_results(rows) rows.each { |row| puts row } end ``` `print_results(finishers)` prints every row and then returns `finishers`. That has practical consequences: - A caller that writes `shown = print_results(finishers)` gets the live array, not a report, and can mutate it. - A test that asserts on the method's return value is really asserting on its argument. - If the method is meant to return nothing useful, ending it with an explicit `nil` makes that intent visible. The contrast with `puts` is a good memory aid: `puts` returns `nil`, `each` returns the collection, and `map` returns a new collection. Three iteration-adjacent calls, three different return values, and each one is regularly misremembered in interviews. ## What to say in an interview State the rule plainly: `each` returns its receiver and ignores the block's values. Then name the consequence: assigning the result gives back the original, aliased object, so use `map` when you want the block's results. Mentioning that `each_with_object` returns its memo and `cycle` returns `nil` shows you know the family, not just one method.

  • What happens if the block pushes onto the same array that Array#each is walking?
    `Array#each` re-reads the array's length on every step, so elements appended during iteration are visited as well. `a.each { |x| a << x }` therefore never terminates. Deleting inside the block has the opposite effect: later elements shift left and one is skipped. Iterate over `a.dup` when the loop must change the array.
  • Why is it useful that each returns the receiver rather than nil?
    It lets a method end with `rows.each { ... }` and still return the collection to its caller, and it lets you chain further calls, such as `rows.each { |r| audit(r) }.size`. The whole each family follows the same rule, except `each_with_object`, which returns its memo, and `cycle`, which returns `nil`.

saying these in an interview costs you the question

  • each returns the values the block produced, just like map
  • each returns nil because it is only meant for side effects
  • each returns a fresh copy, so modifying the result is safe
  • Hash#each returns an array of key-value pairs
  • Deleting elements inside Array#each is safe because each works on a snapshot
open as a page

In Ruby, how do you number marathon finishers from 1 while iterating, and why does each_with_index(1) not do it?

level: juniorimportance: must knowfreq 62%

basics

~10 s

Call each.with_index(1): Enumerator#with_index takes a starting offset. each_with_index always counts from 0 and forwards its arguments to each, so on an Array each_with_index(1) raises ArgumentError instead of shifting the index.

open as a page

In Ruby, why does `rows.inject({}) { |h, r| h[r.bib] = r.secs }` fail, and how does each_with_object avoid it?

level: middleimportance: must knowfreq 55%

basics

~20 s

inject passes the block's return value on as the next memo, and h[k] = v evaluates to v, so the hash is lost after the first row. each_with_object passes the same object every time, ignores the block's value and returns that object.

open as a page

In Ruby, how do each_slice and each_cons differ when walking a marathon results table, and what do they return?

level: middleimportance: should knowfreq 42%

basics

~10 s

each_slice(n) yields disjoint groups of n, with a shorter last group; each_cons(n) yields overlapping windows of n consecutive elements, sliding by one. Both return the receiver in Ruby 4.0; before 3.1 they returned nil.

open as a page

In Ruby, why do Enumerable#reverse_each and Enumerable#cycle buffer every element, and when do Array and Range avoid that?

level: seniorimportance: should knowfreq 22%

basics

~20 s

A plain Enumerable only offers a forward, one-pass each, so reverse_each first collects everything with to_a and cycle records each element to replay later. Array walks its own indexes, and integer Ranges count down, so neither buffers.

open as a page