Question

How to Map and Remove nil Values in Ruby

Question

In Ruby, how can you transform each element in an array with a method that may return either a new value or nil, and then remove the nil results in an idiomatic way?

For example, suppose you have a method like this:

def transform(n)
  rand > 0.5 ? n * 10 : nil
end

And you are currently doing this in two steps:

items.map! { |x| transform(x) }   # [1, 2, 3, 4, 5] => [10, nil, 30, 40, nil]
items.reject! { |x| x.nil? }      # [10, nil, 30, 40, nil] => [10, 30, 40]

You could also build a new array manually:

new_items = []
items.each do |x|
  x = transform(x)
  new_items << x unless x.nil?
end
items = new_items

However, that feels less idiomatic. Is there a cleaner Ruby pattern for mapping over a list while excluding nil values as part of the same operation?

Short Answer

By the end of this page, you will understand how to transform array elements in Ruby and remove nil results cleanly. You will learn when to use map + compact, filter_map, in-place methods like map! and compact!, and how these patterns appear in real Ruby code.

Concept

In Ruby, map is used to transform each element of a collection into a new value. But sometimes a transformation may fail, skip an item, or intentionally return nil. When that happens, you often want to keep only the meaningful results.

This is a very common pattern:

  1. Transform each item
  2. Discard empty results

In Ruby, nil is often used to mean “no value” or “skip this item”. So if your transformation returns either a real value or nil, your next step is usually to remove the nils.

The most common idiomatic approaches are:

  • map { ... }.compact
  • map! { ... }; compact!
  • filter_map { ... } in modern Ruby

Why this matters:

  • It keeps your code expressive and readable.
  • It avoids manual temporary arrays in many cases.
  • It communicates intent clearly: transform values, then keep only valid results.

This pattern appears often in:

  • data cleanup
  • parsing
  • API response processing
  • optional lookups
  • validation pipelines

A key detail: map always returns an array of the same length as the input. If you want the output to be shorter because some items are skipped, you need an extra step like compact, or you use filter_map, which combines both ideas.

Mental Model

Think of map like sending every item through a machine on a conveyor belt.

  • If the machine produces a result, that result goes into the output tray.
  • If the machine produces nil, the tray still gets a placeholder unless you clean it up.

So:

  • map = process every item
  • compact = throw away empty placeholders (nil)
  • filter_map = process the item and only keep real results in one pass

Analogy:

  • map is like stamping every form.
  • nil means the form was rejected.
  • compact removes rejected forms afterward.
  • filter_map only files accepted forms in the first place.
Take Quiz

Syntax and Examples

Core syntax

array.map { |item| transform(item) }.compact

This transforms every item, then removes all nil values.

Example

def transform(n)
  n.even? ? n * 10 : nil
end

items = [1, 2, 3, 4, 5]
result = items.map { |x| transform(x) }.compact

p result
# => [20, 40]

What happens here?

  • 1 becomes nil
  • 2 becomes 20
  • 3 becomes nil
  • 4 becomes 40
  • 5 becomes nil
  • compact removes the nil values

In-place version

If you want to modify the original array:

items = [1, 2, 3, 4, 5]

items.map! { |x| transform(x) }
items.compact!

p items
# => [20, 40]

Modern Ruby: filter_map

If your Ruby version supports it, this is often the cleanest solution:

items = [1, 2, 3, 4, 5]
result = items.filter_map { |x| transform(x) }

p result
# => [20, 40]

filter_map applies the block and keeps only truthy results. Since nil is falsey in Ruby, it gets removed automatically.

Important note about false

filter_map removes both nil and false, because it keeps only truthy values.

[1, 2, 3].filter_map { |n| n == 2 ? false : n }
# => [1, 3]

If you want to remove only nil and keep false, use:

array.map { |x| something(x) }.compact

Step by Step Execution

Consider this example:

def transform(n)
  n > 2 ? n * 10 : nil
end

items = [1, 2, 3, 4]
result = items.map { |x| transform(x) }.compact

Step 1: Start with the original array

items = [1, 2, 3, 4]

Step 2: Run map

Ruby calls the block once for each element.

  • For 1: transform(1) returns nil
  • For 2: transform(2) returns nil
  • For 3: transform(3) returns 30
  • For 4: transform(4) returns 40

So after map, the temporary array is:

[nil, nil, 30, 40]

Step 3: Run compact

compact removes all nil values:

[30, 40]

Final result

result = [30, 40]

In-place trace

items = [1, 2, 3, 4]
items.map! { |x| transform(x) }
# items is now [nil, nil, 30, 40]

items.compact!
# items is now [30, 40]

This version changes the original array instead of creating a separate result array.

Real World Use Cases

This pattern shows up often when a transformation may or may not produce a useful value.

1. Parsing optional data

strings = ["10", "abc", "25"]
numbers = strings.map { |s| Integer(s, exception: false) }.compact
# => [10, 25]

Invalid numbers become nil, then get removed.

2. Looking up database records

user_ids = [1, 2, 999]
users = user_ids.map { |id| User.find_by(id: id) }.compact

Missing records return nil, and you keep only found users.

3. Building API payloads

fields = [:name, :email, :phone]
payload = fields.map { |field| value = user[field]; [field, value] if value }.compact.to_h

Only fields with values are included.

4. Cleaning imported data

rows = ["Alice", "", "Bob", nil]
cleaned = rows.map { |name| name&.strip.presence }.compact

This removes blank or missing entries after transformation.

5. Extracting valid file paths

paths = inputs.map { |path| File.exist?(path) ? path : nil }.compact

Only existing files are kept.

Take Quiz

Real Codebase Usage

In real Ruby projects, developers use this concept in a few common ways.

map + compact for clarity

This is very readable and works well when you explicitly want to remove only nil values.

emails = users.map { |user| user.email if user.active? }.compact

filter_map for combined transform-and-filter logic

This is common in modern Ruby because it is concise.

emails = users.filter_map { |user| user.email if user.active? }

Guard clauses inside the block

Developers often return early from the block when an item should be skipped.

results = records.filter_map do |record|
  next unless record.valid?
  record.id
end

Validation pipelines

A method may return a processed value or nil when input is invalid.

def normalize_phone(phone)
  digits = phone.gsub(/\D/, "")
  return nil unless digits.length == 10
  digits
end

phones = raw_phones.map { |p| normalize_phone(p) }.compact

Avoiding extra loops in business logic

Instead of manually building arrays, developers often prefer collection methods because they show intent more clearly.

eligible_ids = accounts.filter_map do |account|
  account.id if account.enabled?
end

In-place updates when performance or mutation matters

If the same array must be reused, in-place methods may be chosen.

items.map! { |item| expensive_transform(item) }
items.compact!

This is useful when mutation is acceptable and you want to avoid assigning a new variable.

Common Mistakes

1. Using filter_map when false is a valid value

filter_map removes all falsey values, including false.

values = [1, 2, 3]
result = values.filter_map { |n| n == 2 ? false : n }
# => [1, 3]

If false should stay, use:

result = values.map { |n| n == 2 ? false : n }.compact
# => [1, false, 3]

2. Forgetting that map keeps array length the same

Broken expectation:

result = [1, 2, 3].map { |n| n if n.even? }
# => [nil, 2, nil]

map transforms every element, but it does not remove elements.

3. Assuming compact! always returns an array

compact! returns nil if nothing changed.

items = [1, 2, 3]
result = items.compact!
# result is nil

This surprises beginners.

If you need the array value reliably, use compact instead.

result = items.compact

4. Mutating an array by accident with bang methods

Methods ending in ! modify the original object.

items = [1, 2, 3]
items.map! { |n| n * 2 }
# items itself is now changed

Use non-bang methods if you want to preserve the original array.

5. Writing manual loops when intent is simple

This works, but is often more verbose than needed:

result = []
items.each do |x|
  value = transform(x)
  result << value unless value.nil?
end

Prefer:

result = items.map { |x| transform(x) }.compact

or:

result = items.filter_map { |x| transform(x) }

6. Syntax mistakes in the transformation method

Broken code:

def transform(n)
  rand > 0.5 ? n * 10 : nil }
end

There is an extra }.

Correct version:

def transform(n)
  rand > 0.5 ? n * 10 : nil
end

Comparisons

ApproachKeeps original array?Removes only nil?Removes false too?Best use
map { ... }.compactYesYesNoClear and explicit
map! { ... }; compact!NoYesNoIn-place mutation
filter_map { ... }YesNoYesConcise transform + filter
each_with_object([])YesCustomCustomMore control

Examples

map + compact

result = items.map { |x| transform(x) }.compact

Good when you want readability and want to remove only nil.

filter_map

result = items.filter_map { |x| transform(x) }

Good when falsey values should all be excluded.

each_with_object

result = items.each_with_object([]) do |x, arr|
  value = transform(x)
  arr << value unless value.nil?
end

Good when the logic is more complex than a simple transformation.

reject vs compact

If you only want to remove nil, compact is simpler than:

items.reject { |x| x.nil? }

Use reject when the removal rule is more general.

items.reject { |x| x.nil? || x < 0 }
Take Quiz

Cheat Sheet

Quick reference

Transform then remove nil

result = items.map { |x| transform(x) }.compact

In-place version

items.map! { |x| transform(x) }
items.compact!

Modern Ruby

result = items.filter_map { |x| transform(x) }

Rules

  • map transforms every element.
  • map does not remove elements.
  • compact removes nil values only.
  • compact! mutates the array and may return nil if unchanged.
  • filter_map transforms and keeps only truthy results.
  • filter_map removes both nil and false.

Choose this when

  • Use map + compact when you want to remove only nil.
  • Use filter_map when falsey results should be excluded.
  • Use map! and compact! when you want to mutate the original array.
  • Use each_with_object([]) when logic is more complex.

Common patterns

users.map { |u| u.email if u.active? }.compact
users.filter_map { |u| u.email if u.active? }
values.compact
values.compact!

FAQ

Is map.compact the idiomatic Ruby solution?

Yes. It is a common and readable way to transform values and then remove nil results.

What is the difference between compact and reject(&:nil?)?

They can produce the same result for removing nil, but compact is shorter and clearer when nil is the only thing you want to remove.

Should I use filter_map instead of map.compact?

Use filter_map if you want a concise combined operation and you are okay with removing both nil and false values.

Does map! change the original array?

Yes. The bang version mutates the existing array.

Does compact! always return the modified array?

No. It returns nil if no nil values were removed.

How do I keep false values but remove nil?

Use map { ... }.compact, because compact removes only nil.

Is a manual each loop wrong?

No. It is valid Ruby. But for simple transform-and-remove logic, collection methods are usually clearer.

Which approach is best for older Ruby versions?

map { ... }.compact works widely and is the safest choice if filter_map is unavailable.

Take Quiz

Mini Project

Description

Build a small Ruby script that processes a list of user-entered strings. Some strings should be converted into integers, while invalid inputs should be skipped. This demonstrates the common pattern of transforming values and removing nil results cleanly.

Goal

Create a script that converts valid numeric strings into integers and ignores invalid entries using idiomatic Ruby collection methods.

Requirements

  • Start with an array of strings containing valid and invalid numbers.
  • Write a method that returns an integer for valid input and nil for invalid input.
  • Produce a final array containing only valid converted integers.
  • Show both a map + compact solution and a filter_map solution.
  • Print the final cleaned array.
Take Quiz

Keep learning

Related questions

View more