difference between each.with_index and each_with_index in Ruby?
Categories:
Ruby's Iteration Methods: each.with_index
vs. each_with_index

Explore the subtle yet significant differences between Ruby's each.with_index
and each_with_index
methods, understanding their use cases and performance implications.
Ruby provides powerful and expressive ways to iterate over collections. Among the most commonly used are methods that allow access to both the element and its index during iteration. While each_with_index
is a well-known method, you might occasionally encounter each.with_index
. This article delves into the distinctions between these two approaches, explaining their underlying mechanisms, typical use cases, and when to choose one over the other.
Understanding each_with_index
The each_with_index
method is a direct method available on enumerable objects (like Arrays, Hashes, Ranges, etc.). It iterates over the collection, yielding each element along with its corresponding index to the block. This is the most straightforward and idiomatic way to get both the item and its index in Ruby.
# Using each_with_index on an Array
['apple', 'banana', 'cherry'].each_with_index do |fruit, index|
puts "#{index}: #{fruit}"
end
# Output:
# 0: apple
# 1: banana
# 2: cherry
# Using each_with_index on a Hash (yields key-value pair and index)
{a: 1, b: 2, c: 3}.each_with_index do |(key, value), index|
puts "#{index}: #{key} => #{value}"
end
# Output:
# 0: a => 1
# 1: b => 2
# 2: c => 3
Examples of each_with_index
in action
Understanding each.with_index
The each.with_index
construct is fundamentally different. It's not a single method call but rather a method chain. When you call each
without a block, it returns an Enumerator
object. The with_index
method is then called on this Enumerator
. This pattern is part of Ruby's powerful Enumerator
class, which allows for lazy evaluation and chaining of iteration methods.
# Using each.with_index on an Array
['red', 'green', 'blue'].each.with_index do |color, index|
puts "#{index}: #{color}"
end
# Output:
# 0: red
# 1: green
# 2: blue
# Demonstrating the Enumerator object
enumerator_obj = ['one', 'two', 'three'].each
puts enumerator_obj.class # => Enumerator
enumerator_with_index = enumerator_obj.with_index
puts enumerator_with_index.class # => Enumerator::WithIndex
enumerator_with_index.each do |item, index|
puts "#{index}: #{item}"
end
# Output:
# 0: one
# 1: two
# 2: three
Examples of each.with_index
and the underlying Enumerator
flowchart TD A[Collection] --> B{Call `each` without block?} B -- Yes --> C[Returns Enumerator] C --> D{Call `with_index` on Enumerator} D --> E[Returns Enumerator::WithIndex] E --> F{Iterate with block (yields item, index)} B -- No --> G[Call `each_with_index` directly] G --> H{Iterate with block (yields item, index)} F & H --> I[Result: Item and Index]
Flowchart illustrating the difference in execution paths
Key Differences and Use Cases
The primary difference lies in their implementation and flexibility. each_with_index
is a direct method, while each.with_index
leverages Ruby's Enumerator
capabilities. This distinction leads to different use cases:
Directness vs. Flexibility:
each_with_index
is direct and often preferred for simple iterations where you immediately need the index.each.with_index
offers more flexibility because it returns anEnumerator::WithIndex
object, which can be chained with otherEnumerator
methods (e.g.,map
,select
,take
).Chaining Iterators: The
each.with_index
pattern shines when you want to combinewith_index
with other enumerator methods. For example, you might want tomap
over a collection and include the index in the transformation, orselect
elements based on their index.Performance: For simple iterations, the performance difference is negligible. However,
each_with_index
might have a slight edge due to fewer object allocations (it doesn't explicitly create an intermediateEnumerator
object). For most practical applications, this difference is not a concern unless you're dealing with extremely large collections and highly performance-sensitive code.
# Chaining with each.with_index
result = ['a', 'b', 'c', 'd'].each.with_index.map do |char, index|
"Item #{char} at position #{index}"
end
puts result.inspect
# Output: ["Item a at position 0", "Item b at position 1", "Item c at position 2", "Item d at position 3"]
# Selecting based on index
even_indexed_items = ['x', 'y', 'z'].each.with_index.select do |item, index|
index.even?
end.map(&:first) # Extract just the item
puts even_indexed_items.inspect
# Output: ["x", "z"]
# This chaining is not directly possible with each_with_index without an extra step:
# ['x', 'y', 'z'].each_with_index.select { |item, index| index.even? } # This works, but the result is an array of [item, index] pairs
# To get just the items, you'd need another map:
# ['x', 'y', 'z'].each_with_index.select { |item, index| index.even? }.map(&:first)
Demonstrating the power of chaining with each.with_index
each_with_index
is generally the more direct and readable choice for simple iterations where you need both the element and its index. Opt for each.with_index
when you need to chain with_index
with other Enumerator
methods for more complex transformations or filtering.When to Use Which
Choosing between each_with_index
and each.with_index
often comes down to readability and the complexity of your iteration logic.
Use
each_with_index
when:- You need to iterate over a collection and perform an action for each element, directly using its index.
- Your iteration logic is straightforward and doesn't require further chaining of
Enumerator
methods. - You prioritize conciseness for simple loops.
Use
each.with_index
when:- You need to chain
with_index
with otherEnumerator
methods likemap
,select
,reject
,take
,drop
, etc. - You are building a more complex, multi-step transformation pipeline that benefits from
Enumerator
's lazy evaluation and chaining capabilities. - You explicitly want to obtain an
Enumerator
object to pass around or manipulate before iterating.
- You need to chain
Both methods achieve the same end goal of providing an index alongside each element during iteration. The choice largely depends on the context and whether you need the additional flexibility offered by Ruby's Enumerator
class.