rubyguides

Enumerable#flat_map

The flat_map method transforms each element with a block and then flattens the result by one level. It’s equivalent to calling map followed by flatten(1), but more efficient and readable.

Enumerable#flat_map is useful when your block naturally returns arrays. Instead of building a nested structure and flattening it later, you can create the final one-dimensional result in a single pass.

how it works

flat_map combines two operations:

  1. Map — transform each element with a block
  2. Flatten — merge the resulting arrays into one

This matters because the method does both steps for you. The block decides what each element becomes, and Ruby immediately stitches the returned arrays together at one level of depth.

collection.flat_map { |element| block_return }
# Equivalent to: collection.map { ... }.flatten(1)

Basic Usage

Flatten an array of arrays:

The first example is the simplest mental model for flat_map. You hand it arrays, it gives you a single array back, and the block can stay tiny because the flattening happens automatically. Notice that the block just returns the inner element as-is — when the transformation is the identity, flat_map behaves exactly like flatten(1).

numbers = [[1, 2], [3, 4], [5, 6]]
result = numbers.flat_map { |arr| arr }
# => [1, 2, 3, 4, 5, 6]

Transform and flatten in one step:

This second version shows why the method is so common in real code. You can transform each word into a list of characters and skip the separate flatten call entirely. The same pattern applies whenever your transformation naturally produces a collection — think tokenizing strings, splitting comma-separated values, or expanding ranges.

words = ["hello", "world"]
result = words.flat_map { |word| word.chars }
# => ["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"]

extracting nested data

Nested collections are one of the best uses for flat_map. It lets you pull out a repeated field, such as phone numbers or tags, without having to manually build intermediate arrays.

Extract all phone numbers from a collection of users:

This is the sort of problem flat_map handles very naturally. You start with nested arrays inside each record, then collapse them into one list that is easier to display or process.

users = [
  { name: "Alice", phones: ["home: 555-1234", "work: 555-5678"] },
  { name: "Bob", phones: ["mobile: 555-9012"] },
  { name: "Carol", phones: ["home: 555-3456", "work: 555-7890", "mobile: 555-1122"] }
]

all_phones = users.flat_map { |user| user[:phones] }
# => ["home: 555-1234", "work: 555-5678", "mobile: 555-9012", "home: 555-3456", "work: 555-7890", "mobile: 555-1122"]

splitting strings

Break sentences into words:

Splitting text is another good fit because each input sentence naturally produces its own array of words. flat_map lets you collect all of those words without leaving a nested structure behind. The split happens inside the block, so each sentence contributes its own word list and the method stitches everything together in the correct order.

sentences = [
  "Hello world",
  "How are you",
  "Goodbye friend"
]

words = sentences.flat_map { |sentence| sentence.split }
# => ["Hello", "world", "How", "are", "you", "Goodbye", "friend"]

with conditional logic

Conditional returns are another common pattern. When a value should contribute zero, one, or many items, flat_map makes that branching feel natural instead of forcing you to build the final array by hand.

Generate multiple results per element:

This pattern is handy when some values should contribute nothing and others should contribute several items. The method keeps the branching inside the block, while the flattened output stays simple. Returning an empty array to skip an element is a clean way to combine filtering with expansion in a single pass, and the caller never has to deal with nested structures.

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

# Return just the number if even, nothing if odd
result = numbers.flat_map { |n| n.even? ? [n] : [] }
# => [2, 4]

# Return the number and its double if odd
result = numbers.flat_map { |n| n.odd? ? [n, n * 2] : [] }
# => [1, 2, 3, 6, 5, 10]

transforming hashes

Convert an array of hashes into key-value pairs:

This works well when the block already returns an array-like structure, such as the key-value pairs from a hash. flat_map turns that per-record structure into one flat list of pairs suitable for iteration.

configs = [
  { name: "debug", value: true },
  { name: "timeout", value: 30 }
]

pairs = configs.flat_map { |h| h.to_a }
# => [[:name, "debug"], [:value, true], [:name, "timeout"], [:value, 30]]

Practical Examples

The examples below show a few common places where flat_map reads better than map plus flatten. The method becomes especially useful once you are combining transformation with data extraction in the same step.

reading multiple files

Reading several files into one line list is a classic scenario for flat_map. Each file opens and splits independently, and the method collects every resulting line without forcing you to manage a temporary accumulator.

filenames = ["a.txt", "b.txt", "c.txt"]

# Read each file and split into lines
all_lines = filenames.flat_map { |filename| 
  File.read(filename).split("\n") 
}

Reading files this way keeps the example focused on the transformation, not on bookkeeping. Each file becomes an array of lines, and flat_map joins those arrays together for you. This approach scales cleanly to any number of files and keeps the loop logic out of your processing code entirely.

generating permutations

[1, 2, 3].flat_map { |n| [n, -n] }
# => [1, -1, 2, -2, 3, -3]

# Cartesian product
sizes = ["S", "M", "L"]
colors = ["red", "blue"]

options = sizes.flat_map { |size| colors.map { |color| "#{size}-#{color}" } }
# => ["S-red", "S-blue", "M-red", "M-blue", "L-red", "L-blue"]

This kind of nested generation is another place where flat_map reads clearly. You can create combinations in a single expression without building an intermediate list by hand. The Cartesian product example is instructive because it stacks flat_map around an inner map, letting the outer call flatten everything into a single list of formatted options.

building a list of dependencies

projects = [
  { name: "web", deps: ["rails", "redis"] },
  { name: "api", deps: ["rails", "jwt"] },
  { name: "worker", deps: ["redis"] }
]

all_deps = projects.flat_map { |p| p[:deps] }.uniq
# => ["rails", "redis", "jwt"]

The final uniq call is common when multiple items depend on the same library. flat_map gets the list into one dimension, and uniq removes the duplicates that show up across projects.

MethodWhat It Does
mapTransforms each element, returns array of arrays if block returns arrays
flattenMerges nested arrays into one, but doesn’t transform
flat_mapTransforms AND flattens in one pass
collectAlias for map
numbers = [[1, 2], [3, 4]]

numbers.map { |x| x }        # => [[1, 2], [3, 4]]
numbers.flatten              # => [1, 2, 3, 4]
numbers.flat_map { |x| x }   # => [1, 2, 3, 4]

Seeing the three results side by side makes the tradeoff obvious — map keeps nesting, flatten removes nesting, and flat_map does both jobs at once when that is what your block already implies. The table highlights this relationship, but running the examples yourself reinforces how each method handles the same input differently at the array level.

Performance Notes

These notes are worth keeping in mind when you reach for flat_map in larger code. The method is mainly about readability, but the implementation also avoids the extra intermediate array that map.flatten(1) creates.

  • flat_map is more efficient than map.flatten(1) because it avoids creating an intermediate array
  • For very deep nesting, use flatten(n) after map instead
  • When the block returns non-array values, flat_map behaves identically to map

Return Value

Always returns a flat (one-dimensional) array:

That predictable return shape makes flat_map easy to chain. You know you are getting a plain array back, even when each block result started out as its own small collection.

[].flat_map { |x| x }           # => []

[[1, 2], [3, 4]].flat_map { |x| x }  # => [1, 2, 3, 4]

Edge Cases

The edge cases below show what happens when the block returns nil, false, or an empty array. In each case, flat_map still follows the same rule: take the block result and flatten one level.

Block returns nil or false:

[1, 2, 3].flat_map { |n| nil }
# => [nil, nil, nil]

[1, 2, 3].flat_map { |n| false }
# => [false, false, false]

This behavior can be surprising the first time you see it, but it is consistent: flat_map flattens one level, it does not filter values out for you. The method treats nil and false as regular values rather than skipping them, which is important to remember when the block might return something falsy.

Empty arrays in result are preserved then flattened:

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

An empty array simply disappears during the flattening step. That is why flat_map works so well for conditional generation, where some inputs should contribute nothing at all.

See Also