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:
[1, 2, 3].map # => #<Enumerator: [1, 2, 3]:map>
This allows chaining with other Enumerable methods:
[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.
[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 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)
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:
names = ["alice", "bob", "carol"]
names.map(&:capitalize) # => ["Alice", "Bob", "Carol"]
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:
tasks = ["write", "review", "deploy"]
tasks.each_with_index.map { |task, i| "#{i + 1}. #{task}" }
# => ["1. write", "2. review", "3. deploy"]
Practical Examples
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]
Converting types
["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"]
Symbol to proc shorthand
[1, 2, 3, 4, 5].map(&:succ) # => [2, 3, 4, 5, 6]
["hello", "world"].map(&:length) # => [5, 5]
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]]
Flattening after mapping
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]]
Chaining with Other Array Methods
map returns an array, so you can chain it with other Enumerable methods:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.select(&:even?)
.map { |n| n * n }
# => [4, 16, 36, 64, 100]
Common chains
# 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]
# 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]]
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]
Combining with sum
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:
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:
data = [1, 2, 3, 4, 5]
data.map!(&:to_s)
data # => ["1", "2", "3", "4", "5"]
Comparing map vs map!
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)
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