skip to content

What is the difference between Array.prototype.flat and Array.prototype.flatMap, and how deep does each one flatten?

level: middleimportance: should knowfreq 45%

answer

  1. the default depth surprises people
  2. one is map plus one unwrap
  3. no depth parameter on the second
  4. returning an empty array drops it
  5. Infinity for unknown nesting

basics

~10 s

Array.prototype.flat flattens nested arrays to a depth of 1 by default and accepts a depth argument, including Infinity for full flattening. Array.prototype.flatMap maps each element then flattens exactly one level, with no depth option.

solid answer

~40 s

`flat(depth = 1)` returns a new array with nested arrays spliced in, one level deep unless you pass a larger depth or `Infinity` to flatten completely. `flatMap(callback)` is `map` followed by `flat(1)` — it runs the callback per element with `(element, index, array)` and then unwraps exactly one level of the returned values; there is no depth parameter, so a callback returning nested arrays leaves the inner nesting intact. The reason `flatMap` earns its own method is that it lets one pass both expand and drop: return an array of several items to expand an element, and return `[]` to remove it, which is a select-and-transform in a single traversal. Both are ES2019, both return a new array, and both skip empty slots in sparse arrays.

code

javascript · 9 lines
javascript
const nested = [1, [2, [3, [4]]]];
console.log(nested.flat());         // [1, 2, [3, [4]]]
console.log(nested.flat(Infinity)); // [1, 2, 3, 4]

// expand, keep, and drop in one traversal
const out = [1, 2, 3, 4].flatMap(n => (n % 2 ? [n, n * 10] : []));
console.log(out); // [1, 10, 3, 30]

console.log([1, 2].flatMap(n => [[n]])); // [[1], [2]] — only one level unwrapped

go deeper

for a junior

Remember that flat unwraps one level unless you pass a depth, that flat(Infinity) flattens everything, and that flatMap transforms and then unwraps a single level.

for a middle

Explain flatMap as map followed by flat(1) with a fixed depth, and show the expand-or-drop idiom where returning an empty array removes an element and returning several expands it.

for a senior

Judge when the single traversal is worth it on real data volumes, and recognise the smell of a flatMap whose callback never returns an array — a copy dressed up as a flatten.

for a principal

Set expectations for how nested API payloads are normalised: whether unbounded flat(Infinity) on untrusted shapes is acceptable, and where the team should define an explicit schema instead of flattening whatever arrives.

## flat: unwrap nesting by depth `Array.prototype.flat(depth)` returns a new array in which nested array elements are spliced into the result, up to the given depth. The default depth is **1**, which surprises people who expect a full flatten: ```js [1, [2, [3, [4]]]].flat(); // [1, 2, [3, [4]]] [1, [2, [3, [4]]]].flat(2); // [1, 2, 3, [4]] [1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4] ``` `Infinity` is the idiomatic way to flatten an arbitrarily nested structure without knowing its shape. `flat` also removes empty slots left by a sparse array: ```js [1, , 3].flat(); // [1, 3] ``` It never mutates the receiver, and non-array elements pass through untouched. ## flatMap: map, then unwrap one level `Array.prototype.flatMap(callback, thisArg?)` is defined as `map(callback).flat(1)` — but it is specified as a single traversal, not two array allocations. The callback signature is the familiar `(element, index, array)`. ```js ['a b', 'c'].flatMap(s => s.split(' ')); // ['a', 'b', 'c'] ``` The depth is fixed at one and cannot be changed: ```js [1, 2].flatMap(n => [[n]]); // [[1], [2]] — inner arrays survive [1, 2].flatMap(n => [[n]]).flat(); // [1, 2] — explicit second pass needed ``` A callback that returns a non-array value is fine; the value is simply placed in the result as-is, exactly as `map` would. ## Why flatMap exists: expand and drop in one pass The real motivation is that `map` is strictly one-to-one — it can never change the length — while a predicate-based filter can only drop, never transform. `flatMap` is one-to-many, and "many" includes zero: ```js const rows = [ { id: 1, tags: ['a', 'b'] }, { id: 2, tags: [] }, { id: 3, tags: ['c'] }, ]; rows.flatMap(r => r.tags); // ['a', 'b', 'c'] — expand [1, 2, 3, 4].flatMap(n => n % 2 ? [n * 10] : []); // [10, 30] — select and transform together ``` That second line is the pattern worth remembering: returning `[]` deletes the element, returning a one-element array keeps a transformed value, returning several expands it. It replaces a two-step chain with a single traversal and, in code that parses or validates, it reads well as "produce zero or more results per input". ## Common mistakes **Expecting a deep flatten from flatMap.** People write `flatMap` on deeply nested data and are surprised the inner arrays remain. Use `map(...).flat(Infinity)` or `flat(depth)` explicitly. **Forgetting to return an array.** `flatMap(n => n)` behaves like `map(n => n)`; nothing is flattened because the callback never produced an array. Conversely `flatMap(n => [n])` and `map(n => n)` produce the same result — an easy sign the method is being used for no reason. **Reaching for it on huge inputs.** `flatMap` allocates a small array per element for the drop-or-keep idiom. For hot loops over very large inputs, a plain accumulation is cheaper; for ordinary application code the clarity wins. ## Version note `flat` and `flatMap` were added in **ES2019** and are available in every current browser and in Node 11 and later. Before that, the common substitutes were `[].concat(...arrays)` for a single level and a recursive helper for deeper structures. `[].concat(...arrays)` is still occasionally seen, but it spreads the arrays as call arguments and can hit the engine's argument-count limit on very large inputs, which `flat` does not.

  • How do you fully flatten an array whose nesting depth you do not know?
    Call `arr.flat(Infinity)`. The depth argument accepts any number, and `Infinity` unwraps every level regardless of shape. Before ES2019 this needed a recursive helper or repeated `[].concat(...)` calls; today the one-liner is both clearer and safe on large inputs, since it does not spread elements as function arguments.
  • When is flatMap the right tool rather than two separate passes?
    When each input can yield zero, one, or several outputs — parsing lines into tokens, expanding a record's child collection, or validating where invalid rows produce nothing. Returning `[]` drops an element and returning multiple values expands it, all in one traversal. If the operation is genuinely a test and then a transform, saying so with two named steps reads more plainly.
  • What happens if a flatMap callback returns a value that is not an array?
    Nothing special — the value is placed into the result unchanged, exactly as `map` would. Flattening only applies to array return values, and only one level deep. That is why `flatMap(x => x)` on a flat array of numbers is just an expensive copy: with no arrays coming back there is nothing to unwrap.

saying these in an interview costs you the question

  • Says flat fully flattens by default
  • Thinks flatMap accepts a depth argument
  • Believes flatMap mutates the original array
  • Claims flatMap can only expand, never drop elements
  • Assumes flatMap flattens nested results recursively

context