Array#flatten
Returns a new one-dimensional array built from the receiver’s nested contents, which are recursively expanded until no nesting remains.
flatten is a good fit when the structure is nested but the result needs to be flat and easy to iterate over. It keeps the original untouched, which is especially useful when the nested shape is still needed elsewhere.
Nested arrays are recursively expanded until no more arrays remain, so the whole structure is easy to reason about once the call finishes. If you only need part of the depth removed, the optional argument lets you keep the rest of the shape intact.
Basic Usage
nested = [1, [2, 3], [4, [5, 6]]]
nested.flatten
# => [1, 2, 3, 4, 5, 6]
flatten walks the entire structure recursively, collecting all non-array elements into a single flat array. The original is unchanged.
That recursive behavior makes the method predictable because every nested array is handled the same way. The call reads like a direct request to “remove the nesting” and leaves the original array alone.
Specifying Depth
Pass an integer to control how many levels of nesting to flatten:
deep = [1, [2, [3, [4]]]]
deep.flatten(1)
# => [1, 2, [3, [4]]]
deep.flatten(2)
# => [1, 2, 3, [4]]
deep.flatten(0)
# => [1, [2, [3, [4]]]]
A depth of -1 flattens all levels, which is the default behavior. This is equivalent to calling flatten without any arguments, but being able to pass -1 explicitly is useful when the depth value is computed and you want a sentinel that means “no limit” without using a special constant or nil check.
deep.flatten(-1)
# => [1, 2, 3, 4]
A negative depth means “flatten everything”. That is a small but useful shortcut because it avoids having to remember a separate constant or special flag for the fully recursive case.
The bang variant: flatten!
flatten! mutates the array in place and returns self if changes were made, or nil if the array was already flat:
arr = [1, [2, 3], [4, 5]]
arr.flatten!
# => [1, 2, 3, 4, 5]
arr
# => [1, 2, 3, 4, 5]
flat = [1, 2, 3]
flat.flatten!
# => nil
flatten vs flat_map
flat_map combines #map and #flatten in a single pass. Rather than building a nested intermediate array and then collapsing it, flat_map produces a flat result directly. Use it when you need to transform and flatten in one step. This single-pass approach is more efficient when both operations are always needed together:
# flatten after mapping
[1, 2, 3].map { |x| [x, x * 2] }.flatten
# => [1, 2, 2, 4, 3, 6]
# flat_map does the same in one pass
[1, 2, 3].flat_map { |x| [x, x * 2] }
# => [1, 2, 2, 4, 3, 6]
flat_map is more efficient when you always need both operations together because it only traverses the array once.
That efficiency is the main reason to choose flat_map instead of building the intermediate nested array first. When the transformation and flattening are inseparable, the combined method keeps the code shorter and usually faster.
How hashes are handled
flatten only operates on arrays. Hashes are treated as atomic, unflattenable objects:
[1, { a: 2 }, [3, 4]]
# => [1, {:a=>2}, [3, 4]]
[1, { a: 2 }, [3, 4]].flatten
# => [1, {:a=>2}, 3, 4]
Hashes stay intact because flatten only knows how to unwrap arrays. That keeps the behavior predictable and avoids surprising you by breaking apart objects that are not meant to be flattened.
The hash stays intact as a single element. To handle deeply nested structures containing hashes, you need custom logic.
Practical Examples
Converting nested structures
# Tree-like data from a config
tree = ['root', ['branch1', ['leaf1', 'leaf2']], ['branch2', ['leaf3']]]
tree.flatten
# => ["root", "branch1", "leaf1", "leaf2", "branch2", "leaf3"]
This kind of recursive structure is exactly what flatten is built for. The method lets the caller ignore the tree shape once the data has been collected into a single list. The flattened result can be iterated, filtered, or transformed without any awareness of the original nesting.
Flattening API responses
# Multiple API calls return arrays of results
pages = [
[{ id: 1 }, { id: 2 }],
[{ id: 3 }, { id: 4 }],
[{ id: 5 }]
]
all_records = pages.flatten
# => [{:id=>1}, {:id=>2}, {:id=>3}, {:id=>4}, {:id=>5}]
The example works because each page already contains arrays of records. Flattening once at the end is a simple way to collect all of them without writing a nested loop. The result is a single list that can be passed directly to a view or another processing step.
Preserving first level only
# Group data where inner groups matter, but inner elements don't
groups = [[:admin, :editor, :viewer], [:user1, :user2]]
groups.flatten(1)
# => [:admin, :editor, :viewer, :user1, :user2]
Limiting the depth is useful when only the outer grouping needs to disappear. The inner arrays stay intact, which can be handy when they already represent meaningful subgroups.
Performance Notes
flatten creates a brand new array on every call. For large nested structures, this means:
- Memory allocation proportional to the total number of elements
- No way to flatten in place without
flatten! - Repeated flatten calls on the same structure waste memory
If you need to flatten a very large structure, consider whether you can build it flat from the start, or use flatten! to avoid the extra allocation.
That choice is mostly about memory pressure and how many temporary objects you are willing to create. For small arrays the difference is negligible, but for large nested data the allocation cost is worth keeping in mind.
Return Value
| Variant | Return |
|---|---|
flatten | A new flat array |
flatten! | self if modified, nil if already flat |
The return value follows the usual Ruby pattern of making the bang version report whether it actually changed anything. That makes it easy to branch on the result when you need to know whether a mutation happened.
See Also
- /reference/array-methods/array-map/ — transform elements before or after flattening
- /reference/array-methods/array-select/ — filter flattened elements
- /reference/enumerable/enumerable-map/ — map operations on flattened results