rubyguides

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:

MethodReturnsUse case
eachOriginal array (unchanged)Iterating for side effects
mapNew array of block resultsTransforming 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