Overview
Ruby's each
method and Enumerator
class form the foundation of iteration in Ruby. The each
method traverses collections element by element, executing a block for each item. Every enumerable object in Ruby implements each
, making it the base method for iteration patterns across arrays, hashes, ranges, and custom collections.
The Enumerator
class creates iterator objects that encapsulate iteration state. Ruby automatically returns an Enumerator
when enumerable methods are called without blocks. This enables method chaining, lazy evaluation, and external iteration control.
# Basic each iteration
[1, 2, 3].each { |n| puts n }
# => 1
# => 2
# => 3
# Enumerator creation
enum = [1, 2, 3].each
enum.class
# => Enumerator
# External iteration
enum.next
# => 1
enum.next
# => 2
Ruby implements each
differently across collection types. Arrays iterate by index, hashes yield key-value pairs, and ranges generate sequential values. The Enumerator
class provides consistent external iteration regardless of the underlying collection type.
# Hash iteration yields key-value pairs
{a: 1, b: 2}.each { |key, value| puts "#{key}: #{value}" }
# => a: 1
# => b: 2
# Range iteration generates values
(1..3).each { |n| puts n * 2 }
# => 2
# => 4
# => 6
Basic Usage
The each
method accepts a block and executes it for every element in the collection. The method always returns the original object, not the block results. This behavior enables method chaining while maintaining the receiver object.
# Array iteration
numbers = [10, 20, 30]
result = numbers.each { |n| n * 2 }
result.equal?(numbers)
# => true
# Block parameters receive collection elements
words = ['ruby', 'python', 'javascript']
words.each { |word| puts word.upcase }
# => RUBY
# => PYTHON
# => JAVASCRIPT
Hash iteration passes both key and value to the block as separate parameters. Single parameter blocks receive two-element arrays containing key-value pairs.
# Two-parameter block
data = {name: 'Alice', age: 30, city: 'Boston'}
data.each { |key, value| puts "#{key} is #{value}" }
# => name is Alice
# => age is 30
# => city is Boston
# Single parameter block receives arrays
data.each { |pair| puts pair.inspect }
# => [:name, "Alice"]
# => [:age, 30]
# => [:city, "Boston"]
Calling enumerable methods without blocks returns Enumerator
objects. These enumerators support external iteration through next
and peek
methods.
# Creating enumerators
array_enum = [1, 2, 3].each
hash_enum = {a: 1, b: 2}.each_with_index
# External iteration
array_enum.next
# => 1
array_enum.peek
# => 2
array_enum.next
# => 2
# Enumerators are rewindable
array_enum.rewind
array_enum.next
# => 1
Enumerators enable method chaining on collections that don't normally support enumerable methods. The with_index
method adds indices to any enumerator.
# Adding indices to iteration
['a', 'b', 'c'].each.with_index { |char, i| puts "#{i}: #{char}" }
# => 0: a
# => 1: b
# => 2: c
# Chaining with other enumerable methods
[1, 2, 3, 4].each.with_index.select { |num, idx| idx.even? }
# => [[1, 0], [3, 2]]
Advanced Usage
Custom enumerators extend iteration patterns beyond built-in collections. The Enumerator
constructor accepts objects and method names, or blocks that yield values.
# Custom enumerator from object and method
class Counter
def initialize(limit)
@limit = limit
end
def count_up
(1..@limit).each { |n| yield n }
end
end
counter = Counter.new(3)
enum = Enumerator.new(counter, :count_up)
enum.each { |n| puts n * 10 }
# => 10
# => 20
# => 30
Block-based enumerators use the yielder object to generate values dynamically. This pattern creates infinite or computed sequences.
# Fibonacci sequence enumerator
fibonacci = Enumerator.new do |yielder|
a, b = 0, 1
loop do
yielder << a
a, b = b, a + b
end
end
fibonacci.take(8)
# => [0, 1, 1, 2, 3, 5, 8, 13]
# Prime number generator
primes = Enumerator.new do |yielder|
candidates = (2..Float::INFINITY).lazy
candidates.each do |num|
is_prime = (2...num).none? { |i| num % i == 0 }
yielder << num if is_prime
end
end
primes.take(5)
# => [2, 3, 5, 7, 11]
Lazy enumerators defer computation until values are requested. The lazy
method creates lazy enumerators that optimize memory usage for large datasets.
# Lazy evaluation avoids intermediate arrays
(1..1_000_000).lazy.select(&:even?).map(&:to_s).take(3)
# => ["2", "4", "6"]
# Without lazy, this creates large intermediate collections
# (1..1_000_000).select(&:even?).map(&:to_s).take(3)
# Creates array of 500,000 integers, then 500,000 strings
# Combining lazy with custom enumerators
fibonacci.lazy.select(&:even?).take(5)
# => [0, 2, 8, 34, 144]
Enumerator composition builds complex iteration patterns from simple components. Multiple enumerators can be chained, zipped, or combined.
# Parallel iteration with zip
letters = ['a', 'b', 'c'].each
numbers = [1, 2, 3].each
letters.zip(numbers) { |letter, num| puts "#{letter}#{num}" }
# => a1
# => b2
# => c3
# Custom enumerable class
class TodoList
include Enumerable
def initialize
@items = []
end
def add(item, priority: :normal)
@items << {task: item, priority: priority}
end
def each
return enum_for(:each) unless block_given?
@items.sort_by { |item| priority_order(item[:priority]) }.each do |item|
yield item
end
end
private
def priority_order(priority)
{high: 1, normal: 2, low: 3}[priority]
end
end
todos = TodoList.new
todos.add("Buy groceries", priority: :low)
todos.add("Fix bug", priority: :high)
todos.add("Write docs", priority: :normal)
todos.map { |item| item[:task] }
# => ["Fix bug", "Write docs", "Buy groceries"]
Common Pitfalls
The return value of each
frequently confuses developers. Unlike map
or select
, each
always returns the original receiver object, not the block results.
# Common mistake: expecting block results
numbers = [1, 2, 3]
doubled = numbers.each { |n| n * 2 }
doubled
# => [1, 2, 3] # Original array, not [2, 4, 6]
# Correct approach for transformation
doubled = numbers.map { |n| n * 2 }
# => [2, 4, 6]
# Using each for side effects only
numbers.each { |n| puts n * 2 } # Prints doubled values
# => [1, 2, 3] # Still returns original array
Enumerator state persists across calls. Calling next
multiple times advances the internal position, and reaching the end raises StopIteration
exceptions.
# Enumerator exhaustion
enum = [1, 2].each
enum.next # => 1
enum.next # => 2
enum.next # => StopIteration: iteration reached an end
# Safe iteration with rewind
enum.rewind
enum.next # => 1
# Using enumerators in loops
begin
loop { puts enum.next }
rescue StopIteration
puts "Iteration complete"
end
Modifying collections during iteration causes unpredictable behavior. Ruby's each
implementation may skip elements or iterate over newly added items.
# Dangerous: modifying during iteration
numbers = [1, 2, 3, 4, 5]
numbers.each do |n|
numbers.delete(n) if n.even? # Unpredictable results
end
numbers
# => [1, 3, 5] or [1, 3, 4, 5] depending on Ruby version
# Safe approach: iterate over copy
numbers = [1, 2, 3, 4, 5]
numbers.dup.each do |n|
numbers.delete(n) if n.even?
end
numbers
# => [1, 3, 5] # Predictable result
# Alternative: collect indices first
to_remove = []
numbers.each_with_index do |n, i|
to_remove << i if n.even?
end
to_remove.reverse.each { |i| numbers.delete_at(i) }
Lazy enumerators defer computation but maintain references to source objects. This prevents garbage collection and can cause memory leaks with large datasets.
# Memory leak potential
def create_large_enum
large_array = (1..1_000_000).to_a
large_array.lazy.select(&:even?) # Retains reference to large_array
end
enum = create_large_enum
# large_array cannot be garbage collected while enum exists
# Better approach: process immediately
def process_large_data
(1..1_000_000).lazy.select(&:even?).take(100).to_a
end
# No persistent reference to intermediate data
Reference
Core Each Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#each |
&block |
self |
Executes block for each element |
#each_with_index |
&block |
self or Enumerator |
Yields element and index |
#each_with_object(obj) |
obj , &block |
obj |
Yields element and accumulator object |
#reverse_each |
&block |
self or Enumerator |
Iterates in reverse order |
Enumerator Class Methods
Method | Parameters | Returns | Description |
---|---|---|---|
Enumerator.new(obj, method) |
Object , Symbol |
Enumerator |
Creates enumerator from object method |
Enumerator.new(&block) |
&block |
Enumerator |
Creates enumerator from block |
#next |
None | Object |
Returns next value, advances position |
#peek |
None | Object |
Returns next value without advancing |
#rewind |
None | self |
Resets iteration to beginning |
#with_index(offset=0) |
Integer |
Enumerator |
Adds index to yielded values |
#with_object(obj) |
Object |
Enumerator |
Yields values with accumulator object |
#size |
None | Integer or nil |
Returns iteration count if known |
#lazy |
None | Enumerator::Lazy |
Creates lazy enumerator |
Collection-Specific Each Behavior
Collection Type | Block Parameters | Iteration Order | Notes |
---|---|---|---|
Array |
element |
Index order (0, 1, 2...) | Most common usage |
Hash |
key, value |
Insertion order (Ruby 1.9+) | Single param gets [key, value] array |
Range |
element |
Sequential | Generates values dynamically |
String |
character |
Character order | Requires #each_char |
Set |
element |
Unspecified | No guaranteed order |
Struct |
value |
Definition order | Iterates over values only |
Exception Hierarchy
StandardError
└── StopIteration
├── message (String) - Description of termination
└── result - Final value from iteration
Enumerator State Methods
Method | Description | Usage Pattern |
---|---|---|
#feed(value) |
Provides value to yielding block | Advanced generator patterns |
#next_values |
Returns array of all yielded values | Multiple yield per iteration |
#peek_values |
Peeks at array of yielded values | Multiple yield inspection |
Performance Characteristics
Operation | Time Complexity | Memory Usage | Notes |
---|---|---|---|
Array#each |
O(n) | O(1) additional | Direct index access |
Hash#each |
O(n) | O(1) additional | Bucket traversal |
Range#each |
O(n) | O(1) additional | Computed values |
Enumerator#next |
O(1) | O(1) per state | Maintains iteration state |
Lazy#each |
O(1) setup | Deferred | Computation on demand |
Common Enumerator Patterns
# Infinite sequences
naturals = (1..Float::INFINITY).each
# Cycling through values
colors = %w[red green blue].cycle
# Repeating single value
zeros = Enumerator.new { |y| loop { y << 0 } }
# File line iteration
file_enum = File.foreach('data.txt')
# Custom object enumeration
class_enum = Enumerator.new(MyClass.new, :iterate_method)