Question
How can I transform a Ruby array like this:
[x1, x2, x3, ..., xN]
into this form:
[[x1, 2], [x2, 3], [x3, 4], ..., [xN, N + 1]]
In other words, I want to collect each element together with an index-based value, where the first element is paired with 2, the second with 3, and so on. What is the simplest Ruby way to do this?
Short Answer
By the end of this page, you will understand how to transform arrays while also using each element’s position in Ruby. You will learn the most common Ruby patterns for this task, especially map.with_index, how to shift the starting index, and how to choose between map, each_with_index, and similar approaches.
Concept
In Ruby, map is used when you want to transform every element of a collection into a new value. Sometimes, the transformation depends not only on the element itself, but also on its position in the array.
That is where indexed iteration becomes useful.
Ruby gives you a clean way to combine transformation and indexing:
array.map.with_index
This lets you access both:
- the current element
- the current index
Because Ruby array indexes normally start at 0, you can either:
- add an offset manually, or
- tell
with_indexto start counting from a different number
For your example, you want the first element paired with 2, the second with 3, and so on. That means you want the index sequence to begin at 2 instead of 0.
This matters in real programming because many data transformations depend on position:
- numbering rows for export
- creating labels like
item_1,item_2 - building coordinate pairs
- formatting data for APIs
- attaching line numbers to text
Understanding how to map with an index helps you write Ruby code that is both concise and expressive.
Mental Model
Think of map like a factory conveyor belt.
- Each item moves past you one at a time.
- You replace each original item with a new version.
- Sometimes you also need the item’s position on the belt.
with_index is like adding a counter next to the belt.
So instead of seeing only:
x1x2x3
You see:
x1at position2x2at position3x3at position4
Then you package each result as a pair:
[element, index]
That is exactly what map.with_index(2) does for you.
Syntax and Examples
Core syntax
array.map.with_index(start) { |element, index| ... }
arrayis the original collectionmapcreates a new arraywith_index(start)provides the index, beginning fromstart- the block returns the transformed value for each element
Example for this question
items = ["x1", "x2", "x3"]
result = items.map.with_index(2) { |item, i| [item, i] }
p result
# => [["x1", 2], ["x2", 3], ["x3", 4]]
This works because:
"x1"gets index2"x2"gets index3"x3"gets index4
Another example: default index starting at 0
items = ["a", "b", "c"]
result = items.map.with_index { |item, i| [item, i] }
p result
# => [["a", 0], ["b", 1], ["c", 2]]
If you need a different starting point, pass it explicitly.
Equivalent approach using manual offset
items = ["x1", "x2", "x3"]
result = items.map.with_index { |item, i| [item, i + 2] }
p result
# => [["x1", 2], ["x2", 3], ["x3", 4]]
This is also valid, but with_index(2) is usually clearer when the offset is simply a different starting number.
Step by Step Execution
Consider this code:
items = ["x1", "x2", "x3"]
result = items.map.with_index(2) { |item, i| [item, i] }
Here is what happens step by step:
-
itemsis assigned the array:["x1", "x2", "x3"] -
mapstarts iterating over the array. -
with_index(2)tells Ruby:- include an index value
- start counting from
2
-
First iteration:
item = "x1"i = 2- block returns:
["x1", 2] -
Second iteration:
item = "x2"i = 3- block returns:
["x2", 3] -
Third iteration:
item = "x3"i = 4- block returns:
["x3", 4] -
mapcollects all returned values into a new array:
[["x1", 2], ["x2", 3], ["x3", 4]]
The original array is unchanged.
Real World Use Cases
Numbering exported rows
rows = ["Alice", "Bob", "Cara"]
numbered = rows.map.with_index(1) { |name, line| [line, name] }
# => [[1, "Alice"], [2, "Bob"], [3, "Cara"]]
Useful for CSV exports, reports, or invoices.
Building labels for UI data
fields = ["email", "password", "phone"]
labeled = fields.map.with_index(1) { |field, i| [field, "field_#{i}"] }
# => [["email", "field_1"], ["password", "field_2"], ["phone", "field_3"]]
Useful when generating form metadata.
Attaching line numbers to file content
lines = ["first line", "second line"]
numbered = lines.map.with_index(1) { |text, line_no| [line_no, text] }
# => [[1, "first line"], [2, "second line"]]
Useful in parsers, editors, or debugging tools.
Creating structured API payloads
products = ["book", "pen"]
payload = products.map.with_index(100) { |name, id| { id: id, name: name } }
# => [{ id: 100, name: "book" }, { id: 101, name: "pen" }]
Useful when converting plain data into structured records.
Real Codebase Usage
In real Ruby projects, developers often use indexed mapping in a few common ways.
1. Transforming data for output
users.map.with_index(1) do |user, position|
{ rank: position, name: user.name }
end
This is common in serializers, presenters, and report generation.
2. Adding stable display numbers
errors.map.with_index(1) do |message, i|
"#{i}. #{message}"
end
Good for validation summaries or command-line output.
3. Building derived values from both content and position
columns.map.with_index do |column, i|
"col_#{i}_#{column}"
end
Useful in import/export tools and dynamic SQL helpers.
4. Prefer map when you want a new array
If your goal is transformation, use map.
result = items.map.with_index(1) { |item, i| [i, item] }
5. Prefer each_with_index when you only want side effects
If you are printing, logging, or mutating something else, each_with_index may be a better fit.
items.each_with_index do |item, i|
puts "#{i}: #{item}"
end
6. Keep blocks simple
If the mapping logic becomes hard to read, extract it.
def pair_with_position(item, position)
[item, position]
end
result = items.map.with_index(2) { |item, i| pair_with_position(item, i) }
This keeps production code clearer and easier to test.
Common Mistakes
Mistake 1: Using each when you want a transformed array
Broken idea:
items = ["x1", "x2", "x3"]
result = items.each_with_index { |item, i| [item, i + 2] }
p result
# => ["x1", "x2", "x3"]
Why it happens:
each_with_indexiterates, but it returns the original collection- it does not collect the block results into a new array
Use this instead:
result = items.map.with_index(2) { |item, i| [item, i] }
Mistake 2: Forgetting that Ruby indexes start at 0
Broken expectation:
items = ["x1", "x2", "x3"]
result = items.map.with_index { |item, i| [item, i] }
p result
# => [["x1", 0], ["x2", 1], ["x3", 2]]
If you need counting from 2, use:
items.map.with_index(2) { |item, i| [item, i] }
Mistake 3: Mixing up block parameter order
Broken code:
items.map.with_index(2) { |i, item| [item, i] }
Problem:
- the first block parameter is the element
- the second is the index
Correct version:
items.map.with_index(2) { |item, i| [item, i] }
Mistake 4: Using map.with_index on something you do not intend to transform
If you only need to print or log values, map is misleading.
Less ideal:
items.map.with_index { |item, i| puts "#{i}: #{item}" }
Better:
items.each_with_index { |item, i| puts "#{i}: #{item}" }
Mistake 5: Forgetting that map returns a new array
items = ["x1", "x2", "x3"]
items.map.with_index(2) { |item, i| [item, i] }
p items
# => ["x1", "x2", "x3"]
The original array does not change unless you assign the result:
items = items.map.with_index(2) { |item, i| [item, i] }
Comparisons
| Approach | Use when | Returns | Example |
|---|---|---|---|
map.with_index | You want a new transformed array and need the index | New array | `items.map.with_index(2) { |
each_with_index | You want to iterate with index for side effects | Original collection | `items.each_with_index { |
map only | You want a new array but do not need index | New array | `items.map { |
each only | You want side effects and do not need index | Original collection | `items.each { |
with_index(2) vs i + 2
| Style | Example | Notes |
|---|---|---|
| Start index directly | map.with_index(2) | Clear and expressive |
| Add offset manually | `map.with_index { | item, i |
In simple cases like this question, map.with_index(2) is usually the cleanest choice.
Cheat Sheet
Quick reference
Transform array with index
array.map.with_index { |element, index| ... }
Start index from a custom number
array.map.with_index(2) { |element, index| ... }
Example
["x1", "x2", "x3"].map.with_index(2) { |item, i| [item, i] }
# => [["x1", 2], ["x2", 3], ["x3", 4]]
Default index start
- Ruby indexes normally start at
0
Use map when
- you want a new transformed array
Use each_with_index when
- you want to iterate with index
- you do not need a new transformed array
Common pattern
array.map.with_index(1) { |value, i| [i, value] }
Parameter order
|element, index|
Not:
|index, element|
Original array
map does not modify the original array unless you assign the result back.
FAQ
How do I map an array with an index in Ruby?
Use map.with_index:
array.map.with_index { |element, index| ... }
How do I start the index at 1 or 2 in Ruby?
Pass the starting value to with_index:
array.map.with_index(2) { |element, index| ... }
What is the difference between map.with_index and each_with_index in Ruby?
map.with_index creates a new array from the block results. each_with_index just iterates and returns the original collection.
Does map.with_index change the original array?
No. It returns a new array. Assign the result if you want to keep it.
Can I use each_with_index.map instead?
You can in some cases, but map.with_index is usually clearer and more idiomatic for transformations.
Why does my index start at 0 in Ruby?
Ruby arrays are zero-based by default. Use with_index(1) or with_index(2) if you need a different start.
What is the simplest solution for converting [x1, x2, x3] to [[x1, 2], [x2, 3], [x3, 4]] in Ruby?
array.map.with_index(2) { |x, i| [x, i] }
Mini Project
Description
Build a small Ruby script that takes a list of task names and pairs each task with a display number starting from 1. This demonstrates how to transform an array into a new structure while using the position of each element. It is similar to generating numbered menus, to-do lists, or export rows.
Goal
Create a numbered task list where each task becomes a pair of [task_name, number].
Requirements
- Create an array of at least three task names.
- Use Ruby to transform the array into a new array.
- Include a number for each task starting from
1. - Print the final transformed array.
- Do not manually write the output array by hand.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.