Question

Ruby map Method: Transforming Range Values into Arrays

rubydictionaryrangeenumeration

Question

What does the .map method do in this Ruby expression?

params = (0...param_count).map

In particular, what values does the range (0...param_count) contain, and what happens when .map is called without a block?

Short Answer

Ruby's map method visits each item in a collection and builds a new collection from the values returned by a block. In this example, (0...param_count) is an exclusive range of integers. Because no block is supplied to .map, Ruby returns an Enumerator instead of immediately creating an array.

Concept

map is an Enumerable method in Ruby. Its job is to transform every item in a collection.

You give map a block. Ruby runs that block once per item, collects each block result, and returns those results in a new array.

numbers = [1, 2, 3]
doubled = numbers.map { |number| number * 2 }

# doubled is [2, 4, 6]

The original numbers array is unchanged. map creates a separate result.

In the expression below:

params = (0...param_count).map

(0...param_count) is a range that starts at 0 and stops before param_count. However, map has no block telling it how to transform each number. Ruby therefore returns an Enumerator, an object that represents work that can be performed later.

To produce an array, provide a block:

params = (0...param_count).map { |index| "param_#{index}" }

If param_count is 3, params becomes:

["param_0", "param_1", "param_2"]

Mental Model

Think of map as a factory conveyor belt.

  • A collection supplies items to the belt.
  • The block is the machine that changes each item.
  • map puts every changed item into a new box (an array).

For example, a belt receives 0, 1, and 2. A label-making machine turns them into "param_0", "param_1", and "param_2".

If you call map without supplying the machine (a block), Ruby gives you an Enumerator: it is like receiving a scheduled conveyor belt that has not started processing items yet.

Take Quiz

Syntax and Examples

The usual map syntax is:

new_array = collection.map { |item| transformation }

You can also use do and end:

new_array = collection.map do |item|
  transformation
end

Transform an array

names = ["ada", "grace", "linus"]
capitalized_names = names.map { |name| name.capitalize }

puts capitalized_names.inspect
# ["Ada", "Grace", "Linus"]

For each string, the block calls capitalize. The returned strings become the contents of the new array.

Transform a range

squares = (1..4).map { |number| number * number }

puts squares.inspect
# [1, 4, 9, 16]

The inclusive range 1..4 contains 1, 2, 3, and 4.

Use the range from the question

param_count = 3
params = (0...param_count).map { |index| "param_#{index}" }

puts params.inspect
# ["param_0", "param_1", "param_2"]

The three-dot range, 0...param_count, excludes the ending value. With param_count = 3, it produces 0, 1, and 2.

Step by Step Execution

Consider this code:

param_count = 3
params = (0...param_count).map { |index| "param_#{index}" }

Execution proceeds as follows:

  1. param_count is assigned 3.
  2. Ruby creates the exclusive range 0...3.
  3. That range yields the values 0, 1, and 2. It does not yield 3.
  4. map calls the block with index = 0.
    • The block returns "param_0".
  5. map calls the block with index = 1.
    • The block returns "param_1".
  6. map calls the block with index = 2.
    • The block returns "param_2".
  7. map gathers the returned values into a new array and assigns it to params.

The final value is:

["param_0", "param_1", "param_2"]

Without a block:

params = (0...param_count).map

params is an Enumerator, not an array of transformed values. You can inspect it with:

puts params.class
# Enumerator

Real World Use Cases

map is useful whenever one input item should become one output item.

  • Formatting API data: Convert database records into JSON-friendly hashes.

    users.map { |user| { id: user.id, name: user.name } }
    
  • Building request parameters: Create a list of parameter names from a count.

    (0...5).map { |i| "field_#{i}" }
    
  • Converting user input: Turn text values into integers.

    ["10", "20", "30"].map(&:to_i)
    
  • Generating file names: Create predictable paths for exported reports.

    (1..3).map { |page| "report-page-#{page}.pdf" }
    
  • Preparing view data: Convert domain objects into display strings.

    products.map { |product| "#{product.name}: $#{product.price}" }
    
Take Quiz

Real Codebase Usage

In production Ruby code, map is often used to shape data at boundaries: before rendering a view, sending an API response, writing a file, or passing values to another service.

Convert records into response data

response = users.map do |user|
  {
    id: user.id,
    email: user.email,
    active: user.active?
  }
end

This keeps an API response independent from the full database object.

Use map with filter

A common pattern is to remove unwanted values first, then transform the remaining values:

active_emails = users
  .select(&:active?)
  .map(&:email)

select chooses which objects remain; map changes those objects into email strings.

Use the symbol-to-proc shortcut carefully

When calling a no-argument method on every item, Ruby supports a compact form:

names = users.map(&:name)

It is equivalent to:

names = users.map { |user| user.name }

Use the explicit block when the transformation has several steps or a descriptive variable improves readability.

Use lazy enumeration for large or unbounded input

Calling map without a block can be part of a chain that receives a block later:

first_three_squares = (1..).map.lazy { |n| n * n }.first(3)
# [1, 4, 9]

For ordinary finite collections, supply the block directly to map.

Common Mistakes

Forgetting the block

This does not create an array:

params = (0...3).map
puts params.class
# Enumerator

Provide a transformation block when you want results now:

params = (0...3).map { |i| "param_#{i}" }

Expecting map to change the original array

numbers = [1, 2, 3]
numbers.map { |n| n * 2 }

puts numbers.inspect
# [1, 2, 3]

Save the return value:

doubled = numbers.map { |n| n * 2 }

Ruby also has map!, which mutates an array in place, but use it only when changing the original array is intentional.

Using .. when ... is needed

(0..3).to_a
# [0, 1, 2, 3]

(0...3).to_a
# [0, 1, 2]

For indexes and counts, 0...count is usually correct because an array with count elements has valid indexes from 0 through count - 1.

Returning the wrong value from a multi-line block

map collects the final expression from each block execution.

labels = [1, 2].map do |number|
  text = "Item #{number}"
  puts text
end

# labels is [nil, nil]

puts returns nil. Make the desired value the final expression:

labels = [1, 2].map do |number|
  text = "Item #{number}"
  puts text
  text
end

Using map only for side effects

If you only want to print, log, or send an action for every item, use each:

users.each { |user| puts user.name }

Use map when the transformed array is meaningful.

Comparisons

ToolMain purposeReturn valueExample
mapTransform every itemA new array`[1, 2].map {
eachPerform an action for every itemThe original collection`[1, 2].each {
select / filterKeep matching itemsA new array of kept items[1, 2, 3].select(&:odd?) → [1, 3]
reduceCombine items into one resultOne accumulated value[1, 2, 3].reduce(:+) → 6

.. versus ... ranges

RangeIncludes ending value?param_count = 3 result
0..param_countYes0, 1, 2, 3
0...param_countNo0, 1, 2

map versus map!

MethodChanges original array?When to use it
mapNoPrefer this for predictable, non-mutating transformations.
map!YesUse only when replacing the original array is intended.
Take Quiz

Cheat Sheet

# Transform each item and return a new array
result = collection.map { |item| item }

# Range with an exclusive ending value
indexes = (0...count).map { |i| i }

# Range with an inclusive ending value
numbers = (1..4).map { |n| n * 2 }

# Short form: call one method on every item
names = users.map(&:name)

# No block: returns an Enumerator
enumerator = [1, 2, 3].map
  • map runs its block once for each item.
  • The block's final expression becomes the corresponding result item.
  • map normally returns a new array.
  • map does not modify the original collection.
  • 0...count is ideal for zero-based indexes: it yields exactly count numbers.
  • Use each for side effects; use map for transformations.

FAQ

What does .map do in Ruby?

It transforms every item in an enumerable collection using a block and returns the transformed results in a new array.

What does (0...param_count).map return without a block?

It returns an Enumerator. Ruby delays the mapping operation until you provide work to perform, chain it, or otherwise consume it.

Why use 0...param_count instead of 0..param_count?

... excludes the ending number. If param_count is 3, 0...3 gives three indexes: 0, 1, and 2.

Does Ruby map modify the original array?

No. map returns a new array. map! is the mutating version for arrays.

What is the difference between map and each in Ruby?

map is for producing transformed values. each is for actions such as printing, logging, or updating external state.

Can I use map on a Range in Ruby?

Yes. Ranges include Enumerable, so you can map each value in a range into a new array.

What does map(&:to_s) mean?

It is a shorthand for calling to_s on each item. It is equivalent to map { |item| item.to_s }.

Take Quiz

Mini Project

Description

Build a small Ruby helper that generates parameter names for a dynamic form or API request. This demonstrates how an exclusive range and map can create a predictable array from a count.

Goal

Create a method that returns names such as "param_0", "param_1", and "param_2" for a supplied parameter count.

Requirements

Accept a parameter count as an argument. Return one generated name for each parameter. Use an exclusive range so the output contains exactly the requested number of names. Use map with a block to transform indexes into strings. Return an empty array when the count is 0.

Take Quiz

Keep learning

Related questions

View more