Array#each_index
array.each_index { |index| block } -> array each_index iterates over an array by passing each element’s index to the block, instead of the element itself. That makes it the right tool when the position is the thing you care about, not the value.
If you need to look up matching entries in another array, label rows, or update elements in place, each_index gives you the coordinate you need without extra bookkeeping. The method keeps the loop focused on indexes, which often reads more clearly than juggling a separate counter by hand.
Syntax
array.each_index { |index| block } -> array
array.each_index -> Enumerator
Examples
Before looking at specific use cases, it is worth seeing the method in its simplest form. The block receives a single argument, the integer index, and the return value is always the original array regardless of what happens inside the block.
Basic Usage
fruits = ["apple", "banana", "cherry"]
fruits.each_index { |i| puts "Index: #{i}" }
# Index: 0
# Index: 1
# Index: 2
# => ["apple", "banana", "cherry"]
This basic example shows the contract in the simplest possible way. The block receives an integer for each position, and the array itself is returned after iteration so you can keep chaining if needed.
That return value is worth noticing because it means the iterator still behaves like the original array after the block runs. You can print the indexes, inspect them, or keep passing the array along without switching to a different object. The return value is the array itself, which makes each_index behave the way readers expect an iterator to behave in Ruby. It follows the same convention as each, map, and most other Enumerable methods that return a meaningful value after the block finishes.
each_index vs each
This shows the key difference:
letters = ["a", "b", "c"]
# each gives you the element
letters.each { |elem| puts elem }
# a
# b
# c
# each_index gives you the index
letters.each_index { |i| puts i }
# 0
# 1
# 2
The comparison makes the difference obvious: each gives you the element, while each_index gives you the slot. Pick the one that matches the task so the block body stays small and readable. When the block only needs the position to look up values elsewhere, each_index expresses that intent more directly than reaching for a full each_with_index and ignoring the element parameter.
When you are choosing between them, think about what the block actually needs. If the value is only there to be fetched by index, each_index keeps the loop honest and avoids carrying around data you will not use. That small distinction matters most in longer loops where an unused block parameter can confuse the next person who reads the code.
Building an index table
users = ["Alice", "Bob", "Carol"]
users.each_index { |i| puts "#{i + 1}. #{users[i]}" }
# 1. Alice
# 2. Bob
# 3. Carol
This is a common use case when you want human-friendly numbering. The index starts at zero, but the display can still start at one because the block has direct access to the position. Display numbering like this appears often in CLI tools and reports, where the internal zero-based index needs to become a one-based label that readers can scan quickly.
Accessing elements by index during iteration
names = ["John", "Jane", "Jim"]
ages = [30, 25, 40]
names.each_index { |i| puts "#{names[i]} is #{ages[i]}" }
# John is 30
# Jane is 25
# Jim is 40
Parallel arrays are easy to misread if you do not keep the index explicit. each_index makes the relationship clear and helps avoid mixing up positions when the arrays are the same length. This pattern is especially useful when the arrays come from different sources but share the same ordering, such as a names array from one query and an ages array from another.
Modifying array elements at specific indices
numbers = [1, 2, 3, 4, 5]
# Double every element at even index
numbers.each_index { |i| numbers[i] *= 2 if i.even? }
numbers
# => [2, 2, 6, 4, 10]
This pattern is useful when the mutation depends on position instead of content. It keeps the indexing logic in one place, which is usually less error-prone than hand-writing the loop counter. In-place mutation with each_index is a good fit for array transformations where the new value at each position is computed from the position itself rather than from the existing element at that slot.
With with_index (same pattern as each)
items = ["Alpha", "Beta", "Gamma"]
items.each_index.with_index(1) { |idx, num| puts "#{num}. #{idx}" }
# 1. 0
# 2. 1
# 3. 2
The example looks a little unusual because it layers another enumerator on top of the index loop. It is mainly here to show that the result can still participate in normal Ruby iteration patterns.
The point is not the odd-looking call chain, it is that the index sequence still behaves like a normal enumerable. Once you see that, it is easier to imagine filtering or mapping the index stream before using it.
Return Value
each_index returns the array itself:
arr = [10, 20, 30]
result = arr.each_index { |i| puts i }
result.object_id == arr.object_id # => true
That return value is intentional, since it lets you use each_index as part of a longer chain without losing the original array. It behaves like a standard iterator, not a separate collection.
When called without a block, it returns an Enumerator:
[1, 2, 3].each_index
# => #<Enumerator: [1, 2, 3]:each_index>
The enumerator form is helpful when you want to defer the iteration or attach another operation later. It is the same iteration, just packaged so you can decide how to consume it.
The deferred form is especially handy in helper methods, where you may want to build a pipeline first and decide how to render it later. That gives you a little more flexibility without changing the basic behavior of the method.
In practice, that means you can leave the index traversal in place and decide later whether to materialize it or compose it with another enumerable method. That small bit of flexibility keeps the API consistent with the rest of Ruby.
Common use cases
Parallel array iteration
When you have two arrays and need to iterate through them together by position:
products = ["Widget", "Gadget", "Gizmo"]
prices = [100, 50, 75]
products.each_index do |i|
puts "#{products[i]}: $#{prices[i]}"
end
# Widget: $100
# Gadget: $50
# Gizmo: $75
This is a good example of when each_index feels natural, because the shared position is the whole point of the operation. The method keeps the relation between the arrays visible at the call site.
The code also stays easy to scan because the index is the only moving part in the block. If the values themselves are already stored elsewhere, this pattern avoids introducing extra temporary variables.
Selective processing by index
data = [1, 2, 3, 4, 5]
# Process only even indices
data.each_index { |i| data[i] *= 10 if i.even? }
# => [10, 2, 30, 4, 50]
The example is a small reminder that index-based iteration is often about structure, not value. You can target every other slot or every third slot without adding a separate counter variable.
That makes each_index a practical choice when you are mutating a collection in place. The position tells the whole story, and the block can stay focused on the rule instead of the bookkeeping.
Building numbered output
tasks = ["Write tests", "Deploy app", "Update docs"]
tasks.each_index do |i|
puts "[ ] #{i + 1}. #{tasks[i]}"
end
# [ ] 1. Write tests
# [ ] 2. Deploy app
# [ ] 3. Update docs
Numbered output is a nice example of why the method exists: the index gives you the position, and the surrounding text can turn it into whatever display format you need. The logic stays small even when the presentation changes.
When the output needs numbering, the index gives you the count for free. That is simpler than keeping a separate counter synchronized with the loop.
When to Use each_index vs each_with_index
| Method | Yields | Use When |
|---|---|---|
each_index | Index only | You don’t need the element |
each_with_index | Element and index | You need both values |
each | Element only | Index is irrelevant |
If you need both element and index, each_with_index is often cleaner:
items = ["a", "b", "c"]
# With each_index - you fetch the element manually
items.each_index { |i| puts "#{i}: #{items[i]}" }
# With each_with_index - cleaner
items.each_with_index { |item, i| puts "#{i}: #{item}" }
For cases where you only need the index (not the element), each_index makes the intent explicit.
That clarity is the real tradeoff. each_with_index is better when you need both values, but each_index is a cleaner signal when the block only cares about where it is in the array.
Performance
each_index has the same performance characteristics as each. There’s no meaningful overhead difference. Choose based on clarity of intent, not performance.
When the code is clearer with each_with_index, use that instead. each_index is best when the element can be fetched directly from the array and the index is the real star of the loop.
See Also
- Enumerable#each_with_index — iterate with both element and index
- Array#each_slice — iterate over fixed-size groups
- Array#map — transform array elements