flat & flatMap
Nested arrays show up constantly — paginated API responses, grouped records, tag lists per item — and flattening them used to require clumsy reduce + concat recipes. ES2019 added two purpose-built array methods that make this clean: flat, which collapses nested arrays into a single level (or several), and flatMap, which maps each element and then flattens the result one level deep. Both return new arrays and never mutate the original.
flat(depth)
Array.prototype.flat(depth) creates a new array with all sub-array elements concatenated into it, recursively up to the specified depth. The depth argument defaults to 1, so a single call flattens one level. Empty slots in sparse arrays are removed in the process.
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat()); // depth 1
console.log(nested.flat(2)); // depth 2
console.log(nested.flat(Infinity)); // fully flatten any depth
Output:
[ 1, 2, 3, 4, [ 5, 6 ] ]
[ 1, 2, 3, 4, 5, 6 ]
[ 1, 2, 3, 4, 5, 6 ]
Pass Infinity when you don’t know — or don’t care about — how deeply the data is nested and just want a single flat list. Because flat skips empty slots, it doubles as a quick way to drop holes from a sparse array.
const sparse = [1, , 3, , 5];
console.log(sparse.flat()); // holes removed even at depth 1
Output:
[ 1, 3, 5 ]
Note:
flat()only removes holes (empty slots), notundefinedvalues.[1, undefined, 3].flat()still containsundefined. Usefilterif you need to strip those too.
flatMap
flatMap(callback) runs a mapping function over every element and then flattens the result by exactly one level. It is equivalent to map() followed by flat(1), but does it in a single pass, which is slightly more efficient and reads more clearly.
const sentences = ["hello world", "foo bar"];
const mapped = sentences.map((s) => s.split(" ")); // array of arrays
const flat = sentences.flatMap((s) => s.split(" ")); // flattened
console.log(mapped);
console.log(flat);
Output:
[ [ 'hello', 'world' ], [ 'foo', 'bar' ] ]
[ 'hello', 'world', 'foo', 'bar' ]
The callback receives the same arguments as map: (element, index, array). Importantly, flatMap only flattens one level — returning a deeply nested array from the callback will not be flattened further.
Filtering and expanding in one step
A powerful idiom: because returning an empty array [] from the callback contributes nothing to the output, flatMap can simultaneously transform and filter. Return [value] to keep, [] to drop.
const numbers = [1, 2, 3, 4, 5];
// Keep evens, doubled — filter + map in a single pass
const result = numbers.flatMap((n) => (n % 2 === 0 ? [n * 2] : []));
console.log(result);
Output:
[ 4, 8 ]
You can also emit more than one element per input, which map alone cannot do without producing a nested array:
const ranges = [1, 2, 3];
const expanded = ranges.flatMap((n) => [n, n * 10]);
console.log(expanded);
Output:
[ 1, 10, 2, 20, 3, 30 ]
flat vs flatMap
| Method | Mapping callback | Flatten depth | Typical use |
|---|---|---|---|
flat(depth) | No | depth (default 1, Infinity for all) | Collapse existing nested arrays |
flatMap(fn) | Yes | Exactly 1 (not configurable) | Map then flatten, or expand/filter in one pass |
If you need to flatten more than one level after mapping, use map(...).flat(depth) instead — flatMap cannot go deeper than one level.
Real-world example
Suppose an API returns pages of orders and you want a single list of every line item across all pages.
const pages = [
{ orders: [{ items: ["a", "b"] }, { items: ["c"] }] },
{ orders: [{ items: ["d", "e"] }] },
];
const allItems = pages
.flatMap((page) => page.orders) // flatten orders across pages
.flatMap((order) => order.items); // flatten items across orders
console.log(allItems);
Output:
[ 'a', 'b', 'c', 'd', 'e' ]
Chaining flatMap twice unwinds two levels of nesting cleanly, with each call doing one transform-and-flatten step.
Browser and runtime support
Both methods are part of ES2019 and supported in all modern browsers and Node.js 11+. They are safe to use without polyfills in any current environment, including Deno and Bun.
Best Practices
- Reach for
flatMapwhenever you find yourself writing.map(...).flat()— it’s clearer and a touch faster. - Use
flat(Infinity)only when arbitrary depth is genuinely possible; a fixed depth signals intent better. - Remember
flatMapflattens exactly one level — chain calls or fall back tomap().flat(depth)for deeper structures. - Exploit the empty-array trick (
[]to drop,[x]to keep) to combine filtering and mapping in a single readable pass. - Don’t rely on
flat()to removeundefined— it only strips holes; add afilter(Boolean)when you need to drop falsy values. - Both methods return new arrays, so they compose well in chains without side effects.