flat()

ES2019+

Creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.

Syntax

array.flat(depth)

Parameters

depth number optional

The depth level specifying how deep a nested array structure should be flattened (default: 1)

Return Value

Array

A new array with the sub-array elements concatenated

Examples

JavaScript
const nested = [1, [2, [3, [4]]]];
console.log(nested.flat());
console.log(nested.flat(2));
Output:
// [1, 2, [3, [4]]] [1, 2, 3, [4]]

📌 When to Use

Use flat() when an array-of-arrays needs to become a single flat list: merging paginated API responses collected with Promise.all(), collapsing per-group results back into one collection, unwrapping matrix rows for a linear scan, or normalizing data where some entries arrived as single values and others as arrays. The depth parameter is the method's steering wheel - the default of 1 unwraps exactly one layer, an explicit number unwraps that many, and Infinity dissolves arbitrary nesting into a fully flat list, useful for tree-shaped data like nested comment threads or directory listings when only the leaves matter. Choosing neighbors: if each element is about to be MAPPED into an array and then flattened one level, flatMap() does both in one pass and one allocation; if the "nesting" is a fixed, known set of arrays, [...a, ...b, ...c] or concat() states the merge more directly; and if the structure is genuinely recursive and you need to process nodes rather than just collect leaves, an explicit recursive walk keeps parent-child context that flat() discards. A pragmatic bonus: flat() also drops empty slots from sparse arrays, making [1, , 3].flat() a terse hole-removal idiom, though filter(() => true) states that intent more plainly.

⚠️ Common Mistakes

Assuming flat() with no argument flattens everything. The default depth is 1, so [1, [2, [3]]].flat() still contains [3]. Code that "worked" on two-level test data quietly ships nested arrays the moment real data goes one layer deeper - then something downstream calls toFixed() on an array and throws. State the depth you mean, even when it is 1.

Defaulting to flat(Infinity) "to be safe". Infinite depth erases structure you may actually need - coordinate pairs like [[x1, y1], [x2, y2]] flatten into a meaningless number soup - and it recurses through whatever depth the data happens to have, so malformed deeply-nested input costs accordingly. Use the depth that matches the schema; reach for Infinity only when nesting is genuinely unbounded by design.

Expecting flat() to clean out null and undefined. It removes only structural holes (empty slots in sparse arrays); explicit null and undefined values are legitimate elements and pass straight through. Post-process with .filter(x => x != null) when nullish entries should not survive the flattening.

Reaching for flat() on arrays of OBJECTS containing arrays. flat() only unwraps elements that are themselves arrays; it does nothing to {items: [...]} entries. Extracting nested object properties into one list is flatMap() territory: orders.flatMap(o => o.items).

Forgetting that flat() copies rather than mutates: nested.flat() as a bare statement computes a flattened array and discards it, leaving nested exactly as before. Assign the result - and remember the elements inside are still shared references, so the copy is shallow.

✅ Best Practices

Match depth to schema: flat() for one known wrapper layer (paginated results), flat(2) for grid-of-groups shapes, flat(Infinity) only for genuinely recursive data such as nested category trees where any depth is valid. An explicit depth doubles as documentation of the expected structure.

Know the hole-dropping side effect: [1, , 3].flat() returns the dense [1, 3], which repairs sparse arrays produced by Array(n) or stray deletes. If you rely on it, leave a comment - the behavior is spec-guaranteed but surprising enough that reviewers may mistake it for a bug.

Collapse map-then-flat sequences into flatMap(): arr.map(fn).flat() builds and immediately discards an intermediate nested array that arr.flatMap(fn) never creates. Keep plain flat() for data that arrives already nested rather than nested-by-your-own-map.

After Promise.all() over paginated fetches, flatten once at the collection point: (await Promise.all(pages.map(fetchPage))).flat() - the parallel requests return an array per page, and a single flat() turns them into the one list the rest of the code expects.

For very large datasets where the flattened copy would strain memory, iterate the nesting directly instead: for (const group of groups) for (const item of group) processes every leaf with zero additional allocation, at the cost of a less composable shape.

⚡ Performance Notes

flat() allocates one new array sized to the flattened total and copies every leaf element reference into it - O(n) time and space in the output size, with recursion (spec-wise) proportional to the requested depth. Depth 1 flattening is a simple, fast concatenation-style pass; flat(Infinity) must inspect every element for array-ness all the way down, so its cost tracks the actual shape of the data. The copies are shallow reference copies, so element size does not matter, but the result array itself can be a large allocation: flattening 1,000 pages of 1,000 rows materializes a million-slot array in one go, which is normally fine on modern heaps yet worth noticing inside loops or per-request server code - cache the flattened form rather than recomputing it. The main pipeline-level optimization is structural: map(...).flat() performs two passes and two allocations where flatMap() does one of each, and repeated incremental flattening (result = result.flat() in a loop) recopies everything per round - flatten once, at the end, at the depth you need.

🌍 Real World Example

Combining Results from Multiple API Calls

Parallel pagination is the everyday producer of arrays-of-arrays: Promise.all() preserves each request's result as its own array, giving back [[page1 users], [page2 users], [page3 users]] - the nesting is an artifact of HOW the data was fetched, not of what it means. One flat() call erases that artifact and hands the rest of the pipeline the single user list it expects, after which sorting, filtering, and rendering proceed as if the data had arrived in one response. The depth-Infinity snippet at the end demonstrates the other end of the spectrum: recursive structures of unknown depth reduced to their leaf values in a single call.

// Fetch users from multiple pages in parallel
const pagePromises = [
  fetchUsers(page: 1),
  fetchUsers(page: 2),
  fetchUsers(page: 3)
];

const pagesOfUsers = await Promise.all(pagePromises);
// [[user1, user2], [user3, user4], [user5, user6]]

const allUsers = pagesOfUsers.flat();
// [user1, user2, user3, user4, user5, user6]

// Deeply nested example
const nested = [[1, [2, [3, [4]]]]];
const completelyFlat = nested.flat(Infinity);
// [1, 2, 3, 4]

Related Methods