The each Method
The each method is the most fundamental way to iterate over a collection in Ruby. Instead of writing a manual counter loop like you might in other languages, you hand each a block of code, and it runs that block once for every element in the collection. Nearly every other iteration method in Ruby — map, select, reduce, and more — is built on the same idea, so understanding each deeply is the foundation for understanding all of Ruby’s Enumerable module.
Overview: How each Works
each is defined on Array, Hash, Range, and any class that includes the Enumerable module and implements its own each. Conceptually, each does one thing: it walks through the collection from the first element to the last, and for every element it yields that element to the block you provided. “Yielding” means Ruby temporarily jumps into your block, hands it the current element as an argument, runs the block’s code, and then jumps back into each to continue with the next element.
A crucial fact that trips up many newcomers: each always returns the original collection it was called on, not the result of the block. The block’s return value is computed and then thrown away. This makes sense once you think about what each is for — it exists purely for its side effects (printing, logging, appending to some other collection, updating a database, etc.), not for producing a new collection. If you want a transformed collection, you want map, not each.
Under the hood, each is typically implemented in C for the built-in classes (Array#each, Hash#each), which is why it’s so fast, but the effect is exactly as if it were written in Ruby using indices. For your own classes, you can include Enumerable and define just one method, each, and you instantly get map, select, reduce, sort, and dozens of other methods for free — they’re all implemented in terms of repeatedly calling your each.
Because a block is not itself an object in Ruby (unlike a Proc or lambda), each is the mechanism that lets you pass “a chunk of code” around without wrapping it in an object first. Ruby methods can accept exactly one implicit block, and yield (used internally by each‘s implementation) is how that block gets invoked.
Syntax
collection.each do |element|
# code that uses element
end
collection.each { |element| puts element }
- collection — any object that responds to
each: anArray,Hash,Range,Set, or a custom Enumerable. - do…end — the multi-line block form; conventionally used for blocks with more than one line.
- { } — the curly-brace block form; conventionally used for short, single-line blocks.
- |element| — the block parameter(s). For an
Array, this is one variable per element. For aHash, Ruby automatically destructures each[key, value]pair, so you typically write|key, value|. - The return value of
collection.each { ... }is alwayscollectionitself.
Examples
Example 1: Iterating over an Array
fruits = ["apple", "banana", "cherry"]
fruits.each do |fruit|
puts "I like #{fruit}"
end
I like apple
I like banana
I like cherry
For each string in fruits, Ruby assigns it to the block variable fruit and runs puts "I like #{fruit}". The block runs three times, once per element, in order.
Example 2: each Returns the Original Collection
numbers = [1, 2, 3, 4, 5]
result = numbers.each do |n|
n * 2
end
puts result.inspect
[1, 2, 3, 4, 5]
Even though the block computes n * 2 for every element, that value is discarded. result is set to whatever each returned, which is the original numbers array, completely unchanged. This is the single most important thing to internalize about each: it is for side effects, not for building a new value.
Example 3: Iterating a Hash and Using each_with_index
scores = { "alice" => 92, "bob" => 85, "carol" => 78 }
scores.each do |name, score|
puts "#{name.capitalize}: #{score}"
end
["x", "y", "z"].each_with_index do |letter, index|
puts "#{index}: #{letter}"
end
Alice: 92
Bob: 85
Carol: 78
0: x
1: y
2: z
For a Hash, each element yielded is actually a two-element array, [key, value]. Writing |name, score| instead of a single |pair| lets Ruby automatically destructure that array into two variables. Ruby’s hashes remember insertion order, so the iteration always visits "alice", "bob", then "carol", in the order they were added. each_with_index is a related Enumerable method that yields both the element and its position, useful when you need to know where you are in the collection.
How It Works Step by Step
When Ruby evaluates numbers.each { |n| ... }, roughly the following happens:
- Ruby locates the
eachmethod by looking up the method lookup chain starting atnumbers‘s singleton class, thenArray, then any modulesArrayincludes, and so on — the same ancestor-chain lookup used for every method call in Ruby. each‘s internal implementation begins walking the array from index 0.- For each element, it calls
yield(element), which transfers control into your block, bindingelementto the block parametern. - Your block runs to completion; its final expression’s value is computed but not stored anywhere by
each. - Control returns to
each, which advances to the next element and repeats. - Once every element has been yielded,
eachreturns the original collection.
One important wrinkle: if the block executes break, iteration stops immediately and each returns whatever value was passed to break instead of the collection.
result = [1, 2, 3, 4].each do |n|
break "found #{n}" if n == 3
end
puts result
found 3
Here the loop reaches n == 3, executes break "found 3", and each‘s call immediately evaluates to "found 3" rather than [1, 2, 3, 4]. This is different from a plain return inside the block: since a block is not a lambda, return would exit the enclosing method entirely (or raise a LocalJumpError at the top level), whereas break only exits the each call itself.
Common Mistakes
Mistake 1: Using each When You Mean map
Because each runs a block for every element, it’s tempting to assume it also collects the block’s results into a new array. It does not.
numbers = [1, 2, 3]
doubled = numbers.each { |n| n * 2 }
puts doubled.inspect
[1, 2, 3]
doubled is just the original array, unchanged — not the doubled values. Use map when you want a new, transformed array:
numbers = [1, 2, 3]
doubled = numbers.map { |n| n * 2 }
puts doubled.inspect
[2, 4, 6]
Mistake 2: Mutating a Collection While Iterating It With each
Modifying an array’s length (adding or removing elements) while each is walking through it by index is unsafe — elements can shift out from under the iterator and get silently skipped.
items = ["a", "b", "c", "d"]
items.each do |item|
items.delete(item) if item == "b"
end
puts items.inspect
["a", "c", "d"]
This looks like it should just remove "b", but watch closely: after "b" is deleted, the array becomes ["a", "c", "d"] and every later element shifts one position to the left. The iterator’s internal index moves on to position 2 as usual, but position 2 now holds "d" instead of "c" — so "c" is silently never visited by the block. The output happens to look correct here only because the condition didn’t need to match "c"; in general this pattern is a real bug source. The safe fix is to build a new collection instead of mutating the one you’re iterating:
items = ["a", "b", "c", "d"]
items = items.reject { |item| item == "b" }
puts items.inspect
["a", "c", "d"]
reject (and its opposite, select) return a brand-new array, leaving the original untouched while it’s being read, so nothing shifts mid-iteration.
Best Practices
- Reach for
eachonly when you want side effects (printing, logging, writing to a file, pushing into an external array). If you want a new transformed collection, usemap; if you want a filtered subset, useselectorreject; if you want to combine everything into one value, usereduce. - Prefer
do...endfor multi-line blocks and{ }for short one-liners — this is the community-standard style and makes code easier to scan. - Never rely on the return value of
eachfor anything other than “the original collection, or abreakvalue.” - Never add or remove elements from the exact collection you’re iterating over with
each. Iterate over a copy (collection.dup) or, better, build the result with a non-mutating method likeselect/reject/map. - When iterating a
Hash, destructure directly in the block parameters (|key, value|) rather than accepting a single pair and indexing into it — it reads more clearly. - If you only need the index alongside the element, use
each_with_indexrather than manually tracking a counter variable. - Remember Ruby’s truthiness rules while writing conditions inside a block: only
nilandfalseare falsy, so an empty array, an empty string, and0are all truthy and will not skip anifcheck.
Practice Exercises
- Given
words = ["ruby", "is", "fun"], useeachto print each word in uppercase, one per line. - Given
inventory = { "apples" => 10, "pears" => 0, "plums" => 5 }, useeachto print"item is out of stock"for any item whose count is0, and"item: count in stock"otherwise. (Hint: remember that0is truthy in Ruby — check with== 0, not by treating the count as a boolean.) - Given
numbers = [3, 7, 2, 9, 4], useeachtogether withbreakto find and print the first number greater than5, then stop iterating. Expected output:7.
Summary
eachruns a block once per element of a collection, purely for side effects.eachalways returns the original collection (or abreakvalue), never the block’s results — usemapif you need a transformed collection.- For a
Hash, each yielded element is a[key, value]pair, which you can destructure directly as|key, value|. each_with_indexyields both the element and its position when you need to track location.- Never mutate the length of a collection you are actively iterating with
each— elements can be silently skipped; iterate a copy or build a new collection instead. breakinside aneachblock stops iteration immediately and becomes the call’s return value; a barereturnbehaves differently because a block is not a lambda.
