Array#map
What does map do?
map transforms every element in an array by passing it through a block, and returns a new array containing the results. The original array is left unchanged.
Think of it as: “take each item, do something to it, and collect the results.”
Array#map is defined directly on the Array class, but Array includes Enumerable — so it behaves identically to Enumerable#map. Array overrides it with an optimised implementation.
Basic Usage
With a block
[1, 2, 3].map { |n| n * 2 } # => [2, 4, 6]
["hello", "world"].map(&:upcase) # => ["HELLO", "WORLD"]
[1, 2, 3, 4, 5].map { |n| n**2 } # => [1, 4, 9, 16, 25]
Without a block
When called without a block, map returns an Enumerator:
The three map calls above show the basic transformation pattern: a block receives each element and returns a new value. map collects all return values into a new array of the same length. The original array is never modified — map always returns a fresh array.
[1, 2, 3].map # => #<Enumerator: [1, 2, 3]:map>
This allows chaining with other Enumerable methods:
When map is called without a block, it returns an Enumerator. This lazy enumerator can be stored, passed as an argument, or chained with other enumerator methods. It defers the actual computation until a block is provided or a consuming method like to_a is called.
[1, 2, 3, 4, 5].map.with_index { |n, i| [i, n] }
# => [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]]
collect: The Alias
collect is an exact synonym for map in Ruby. Same implementation, same return type, same behaviour. Use whichever reads more naturally.
Chaining map.with_index pairs each element with its zero-based index, producing an array of [index, value] pairs. The with_index method is only available on enumerators, which is why it must follow a blockless call to map rather than being applied after the block.
[1, 2, 3].map { |n| n * 2 } # => [2, 4, 6]
[1, 2, 3].collect { |n| n * 2 } # => [2, 4, 6]
map is more common in modern Ruby code. collect is the historical name inherited from Smalltalk, Ruby’s predecessor in this design.
Both map(&:to_s) and collect(&:to_s) produce identical results — they call the same C function. The choice is purely stylistic. map is more common in modern Ruby code and is the name used in most documentation and tutorials.
# Both are identical:
numbers.map(&:to_s)
numbers.collect(&:to_s)
map vs each: the key difference
The most common source of confusion is mixing up map with each. They look similar but behave very differently:
| Method | Returns | Use case |
|---|---|---|
each | Original array (unchanged) | Iterating for side effects |
map | New array of block results | Transforming elements |
numbers = [1, 2, 3]
result = numbers.each { |n| n * 2 }
result # => [1, 2, 3] - original, unchanged!
result = numbers.map { |n| n * 2 }
result # => [2, 4, 6] - new array with transformed values
The example above shows each printing to the console but returning the original array unchanged. This is the correct use of each — side effects only. For cases where you actually want the transformed values back, choose map instead:
The table above shows that each returns the original array regardless of what the block does, while map returns a new array of block results. This is the fundamental distinction:
When to use each
Use each when you want to perform side effects — printing, writing to a file, updating external state:
[1, 2, 3].each { |n| puts n * 2 }
# Output:
# 2
# 4
# 6
# => [1, 2, 3]
When to use map
Use map when you want to derive a new collection from an existing one — every element passes through the block and the results form a new array. This is the core pattern for data transformation in Ruby, and it is what separates functional pipelines from imperative loops:
names = ["alice", "bob", "carol"]
names.map(&:capitalize) # => ["Alice", "Bob", "Carol"]
The each example above prints values but returns the original array unchanged. That is the core difference: each is for side effects, map is for deriving a new collection. When you need the index during transformation, chain with_index:
Using map with Index
Chain with_index to access the index of each element:
fruits = ["apple", "banana", "cherry"]
fruits.map.with_index { |fruit, i| "#{i + 1}. #{fruit}" }
# => ["1. apple", "2. banana", "3. cherry"]
fruits.map.with_index { |fruit, i| [i, fruit] }
# => [[0, "apple"], [1, "banana"], [2, "cherry"]]
You can also combine with each_with_index when you need the index during iteration:
Combining each_with_index.map gives you both the element and its index during transformation. The each_with_index enumerator yields [element, index] pairs, and map collects the transformed results. This is an alternative to map.with_index when you prefer the index to come second.
tasks = ["write", "review", "deploy"]
tasks.each_with_index.map { |task, i| "#{i + 1}. #{task}" }
# => ["1. write", "2. review", "3. deploy"]
Practical Examples
Chaining map.with_index gives you access to both the element and its position, which is useful for numbered lists or position-dependent transformations. The practical examples below show how map handles common data-processing tasks in everyday Ruby code, from extracting attributes to building nested transformations:
Extracting attributes
users = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "Carol", age: 35 }
]
users.map { |u| u[:name] } # => ["Alice", "Bob", "Carol"]
users.map { |u| u[:age] } # => [30, 25, 35]
Extracting attributes with map turns an array of hashes into an array of values from a single key. The same pattern works for converting between types — transform an array of strings into integers, or floats into integers:
Converting types
Extracting a single key from an array of hashes with map { |u| u[:name] } produces an array of values. This pattern is fundamental in Ruby applications that work with structured records: you load a collection of hashes and then project out the field you need.
["1", "2", "3"].map(&:to_i) # => [1, 2, 3]
[1.5, 2.7, 3.1].map(&:to_i) # => [1, 2, 3]
[100, 200, 300].map { |n| n.to_s } # => ["100", "200", "300"]
Converting types with map is one of the most common transformations in Ruby scripts. When the block is a single method call with no arguments, the symbol-to-proc shorthand makes the code even terser:
Symbol to proc shorthand
map(&:to_i) converts each string to an integer by calling to_i on every element. Ruby’s &:method_name syntax is shorthand for { |obj| obj.method_name }. This shortcut works for any argument-less method and is widely used in Ruby pipelines.
[1, 2, 3, 4, 5].map(&:succ) # => [2, 3, 4, 5, 6]
["hello", "world"].map(&:length) # => [5, 5]
The symbol-to-proc shorthand &:succ and &:length is concise and reads clearly for simple transformations. For more complex operations on nested data structures, use a block with another map call inside it:
Nested mapping
Transform nested arrays by mapping over each inner array:
matrix = [[1, 2], [3, 4], [5, 6]]
matrix.map { |row| row.map { |n| n * 10 } }
# => [[10, 20], [30, 40], [50, 60]]
Nested mapping transforms each inner array independently, giving you a two-dimensional result. When you want a flat list instead, destructure the inner arrays in the block or chain flatten afterward:
Flattening after mapping
Nested map calls transform two-dimensional data independently: each inner array gets its own transformation. This is distinct from flatten followed by a single map, which would lose the row structure. Use nested mapping when the grouping of elements matters.
pairs = [[1, 2], [3, 4], [5, 6]]
pairs.map { |a, b| a + b } # => [3, 7, 11]
pairs.map { |a, b| [a * 2, b * 2] } # => [[2, 4], [6, 8], [10, 12]]
Flattening after nested mapping is a common pattern for working with two-dimensional arrays. The result is always another array, which means you can keep chaining without breaking your pipeline:
Chaining with other array methods
Because map returns a new array, you can chain it with any other Enumerable method. The order of operations matters — filtering before transforming is often more efficient because you process fewer elements:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.select(&:even?)
.map { |n| n * n }
# => [4, 16, 36, 64, 100]
Common chains
Filtering even numbers first and then squaring them is more efficient than the reverse order: select reduces the collection size before map processes each element. This select-then-map pattern is the idiomatic way to filter and transform data in Ruby pipelines.
# Select then map
[1, 2, 3, 4, 5].select(&:odd?).map { |n| n * 10 } # => [10, 30, 50]
# Map then select — order matters!
[1, 2, 3, 4, 5].map { |n| n * 10 }.select(&:even?) # => [20, 40]
The order of chaining determines which elements get transformed and which get filtered. `select` then `map` transforms only the survivors, while `map` then `select` transforms everything first:
# Map then reject
[1, 2, 3, 4, 5].map { |n| n * 2 }.reject { |n| n < 5 }
# => [6, 8, 10]
# Map then flatten
[[1, 2], [3, 4]].map { |arr| arr.map { |n| n + 1 } }
# => [[2, 3], [4, 5]]
The chain select then map filters first and transforms second. Reversing the order — map then select — produces different results because map transforms every element before filtering. When some transformations may produce nil, use compact to clean up:
Using compact after map
Remove nil values after a conditional transformation:
["1", "two", "3", nil, "four"].map { |s| s.to_i if s.is_a?(String) }.compact
# => [1, 3, 4]
Using compact after a conditional map removes nil entries cleanly, keeping only the successfully transformed values. When the transformation produces numbers, chaining sum aggregates the results in a single expression:
Combining with sum
When a conditional map produces nil for some elements, chaining compact removes them. The is_a?(String) guard ensures to_i only runs on string values, avoiding errors on nil or other types. This map-then-compact pattern is cleaner than filtering separately before transforming.
prices = [10, 20, 30]
prices.map { |p| p * 1.2 }.sum # => 72.0 (with tax)
mutating with map! (bang variant)
The bang variant map! mutates the original array in place instead of returning a new one:
Adding tax to prices via map { |p| p * 1.2 } and then calling sum produces the total in one expression. This map-then-sum pipeline is one of the most common data-processing patterns in Ruby, and it works because each method returns a value that feeds into the next.
numbers = [1, 2, 3, 4, 5]
result = numbers.map! { |n| n * 2 }
numbers # => [2, 4, 6, 8, 10]
result # => [2, 4, 6, 8, 10]
result.equal?(numbers) # => true (same object)
When to use map!
Use map! when you want to reuse the same variable rather than allocating a new array:
After map! runs, result.equal?(numbers) returns true because both variables point to the same array object. The method mutates the receiver in place and returns it — a pattern Ruby uses consistently for bang methods across Array, String, and Hash.
data = [1, 2, 3, 4, 5]
data.map!(&:to_s)
data # => ["1", "2", "3", "4", "5"]
Comparing map vs map!
Converting an array of numbers to strings in place is a practical use of map! when you no longer need the original numeric values. The bang variant is also useful in tight loops where allocating a new array on every iteration would create GC pressure.
original = [1, 2, 3, 4, 5]
# map returns a new array, original unchanged
transformed = original.map { |n| n * 2 }
original # => [1, 2, 3, 4, 5]
transformed # => [2, 4, 6, 8, 10]
# map! modifies the original
original.map! { |n| n * 2 }
original # => [2, 4, 6, 8, 10]
Performance Considerations
map has O(n) time complexity — it visits each element exactly once. It always processes the full collection and creates a new array of the same size.
Memory usage
map creates and returns a new array. For very large collections, this allocation can matter:
# Memory: creates a full new array
squares = (1..1_000_000).map { |n| n**2 } # lots of memory
# If you just need to print squares:
(1..1_000_000).each { |n| puts n**2 } # no extra array created
Bang variant performance
map! avoids allocating a new array, which reduces memory pressure for large arrays. However, it still iterates through all elements — the performance benefit is solely in avoiding allocation.
Lazy evaluation with lazy.map
For large or infinite collections, use lazy to defer evaluation:
# Eager: processes all immediately
(1..10).map { |n| n * 2 }.first(3) # => [2, 4, 6]
# (actually computes all, then takes first 3)
# Lazy: processes only what's needed
(1..Float::INFINITY).lazy.map { |n| n * 2 }.first(3)
# => [2, 4, 6]
# (stops after producing 3 results)
The comparison above shows that map preserves the original array while map! modifies it in place. Choose map! when you want to reuse the same variable without allocating a new array, and map when you need the original data intact. For related methods that filter or transform collections differently:
See Also
- /reference/enumerable/enumerable-map/ — The Enumerable version of map (identical behaviour)
- /reference/enumerable/enumerable-select/ — Returns elements matching a condition (filter, not transform)
- /reference/array-methods/array-reject/ — Returns elements for which the block evaluates to false
- /reference/enumerable/enumerable-filter-map/ — Combines filtering and transformation in one pass