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:
- Transform each item
- 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 { ... }.compactmap! { ... }; 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 itemcompact= throw away empty placeholders (nil)filter_map= process the item and only keep real results in one pass
Analogy:
mapis like stamping every form.nilmeans the form was rejected.compactremoves rejected forms afterward.filter_maponly files accepted forms in the first place.
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?
1becomesnil2becomes203becomesnil4becomes405becomesnilcompactremoves thenilvalues
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)returnsnil - For
2:transform(2)returnsnil - For
3:transform(3)returns30 - For
4:transform(4)returns40
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.
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
| Approach | Keeps original array? | Removes only nil? | Removes false too? | Best use |
|---|---|---|---|---|
map { ... }.compact | Yes | Yes | No | Clear and explicit |
map! { ... }; compact! | No | Yes | No | In-place mutation |
filter_map { ... } | Yes | No | Yes | Concise transform + filter |
each_with_object([]) | Yes | Custom | Custom | More 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 }
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
maptransforms every element.mapdoes not remove elements.compactremovesnilvalues only.compact!mutates the array and may returnnilif unchanged.filter_maptransforms and keeps only truthy results.filter_mapremoves bothnilandfalse.
Choose this when
- Use
map + compactwhen you want to remove onlynil. - Use
filter_mapwhen falsey results should be excluded. - Use
map!andcompact!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.
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
nilfor invalid input. - Produce a final array containing only valid converted integers.
- Show both a
map + compactsolution and afilter_mapsolution. - Print the final cleaned array.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.