Flattening Arrays in JavaScript

Abstract image used to represent Flattening Arrays in JavaScript
Image by Chris Robert.

Working with nested arrays is common in JavaScript, especially when dealing with complex data structures in booking systems or ecommerce platforms. In simple terms, 'flattening' an array means taking values out of nested arrays and placing them into a new array in the same lefttoright order. Only arrays are opened up. Values that are not arrays are kept as they are.

In this article, I will cover different ways to flatten arrays in JavaScript, from the modern flat() method to recursive and stackbased approaches. I will also explain how depth affects the result, what happens to values that are not arrays, and when a custom helper may be more appropriate than the builtin method.


What Does It Mean to Flatten an Array?

Starting with the basics, flattening removes one or more levels of array nesting. It does not change the values themselves; it only changes where those values sit in the array structure. For example:

const nestedArray = [1, [2, 3], "four", [{ five: 5 }], null];

const flattenedArray = nestedArray.flat();

console.log(flattenedArray);
[1, 2, 3, "four", { five: 5 }, null]

In this example, [2, 3] and [{ five: 5 }] are nested arrays, so their contents move into the new array. The string, object, and null value are not arrays, so they are copied into the result unchanged.

const deeplyNestedArray = [1, [2, [3, [4]]]];

console.log(deeplyNestedArray.flat());
// [1, 2, [3, [4]]]

console.log(deeplyNestedArray.flat(Infinity));
// [1, 2, 3, 4]

By default, flat() only removes one level of nesting. A deep flatten removes every nested array until no arrays remain inside the result.


Using Array.prototype.flat()

The simplest way to flatten arrays in modern JavaScript is with the .flat() method, introduced in ECMAScript 2019 (ES10).

Basic Usage

const nestedArray = [1, [2, 3], [4, 5]];
const flatArray = nestedArray.flat();

console.log(flatArray);  // Output: [1, 2, 3, 4, 5]

However, by default, .flat() only flattens the array one level deep.

Flattening Deeper Arrays

To flatten arrays with deeper nesting, you can specify the depth as an argument. For example:

const deepNestedArray = [1, [2, [3, [4, 5]]], 6];
const fullyFlatArray = deepNestedArray.flat(3);

console.log(fullyFlatArray);  // Output: [1, 2, 3, 4, 5, 6]

Or, if you want to flatten to an infinite depth, you can quite literally pass Infinity in:

const fullyFlattened = deepNestedArray.flat(Infinity);
console.log(fullyFlattened);  // Output: [1, 2, 3, 4, 5, 6]

Empty Slots in Sparse Arrays

flat() also removes empty slots in sparse arrays at the levels it flattens:

const sparseArray = [1, , 3, [4, , 6]];

console.log(sparseArray.flat());
// [1, 3, 4, 6]

An empty slot is not the same thing as undefined. If an array contains undefined as an actual value, that value remains in the result.

When .flat() Might Not Be Available

The web moves fast, and 2019 wasn't all that long ago, so there's a chance that it may not be supported in your browser support matrix, especially if you're still targeting IE11. In those situations, alternative methods would be required.


Flattening Arrays Without .flat()

Using Recursion

Here is a recursive deepflattening helper in TypeScript. Compile the type annotations away before running it as JavaScript, and check that the target runtime supports the array methods it uses:

const flattenArray = (array: unknown[]): unknown[] =>
  array.reduce<unknown[]>(
    (result, value) =>
      Array.isArray(value)
        ? result.concat(flattenArray(value))
        : result.concat([value]),
    []
  );

const nestedArray = [1, [2, [3, [4, 5]]], 6];
console.log(flattenArray(nestedArray));   // Output: [1, 2, 3, 4, 5, 6]

The helper opens only arrays. Wrapping a nonarray in [value] before concatenating keeps that value intact even if it has Symbol.isConcatSpreadable set. The reduce() traversal skips sparse holes at each flattened level, but keeps explicit undefined values. Deep recursion can still exhaust the call stack.

Using a Stack

This TypeScript version uses an explicit stack. Its indexed loops skip sparse holes, matching the recursive helper's policy, and add values individually rather than passing a very wide array as function arguments:

const flattenUsingStack = (array: unknown[]): unknown[] => {
  const stack: unknown[] = [];
  const result: unknown[] = [];

  const pushPresentValues = (values: unknown[]): void => {
    for (let index = 0; index < values.length; index += 1) {
      if (index in values) {
        stack.push(values[index]);
      }
    }
  };

  pushPresentValues(array);
  while (stack.length) {
    const next = stack.pop();
    if (Array.isArray(next)) {
      pushPresentValues(next);
    } else {
      result.push(next);
    }
  }

  return result.reverse();
};

const nestedArray = [1, [2, [3, [4, 5]]], 6];
console.log(flattenUsingStack(nestedArray));  // [1, 2, 3, 4, 5, 6]

This avoids recursive call depth and spreadargument limits. It still needs memory for the pending values and the result, so test with representative inputs; neither helper is intended for arrays that contain themselves.


Performance Considerations

  • .flat() is usually the clearest option in modern JavaScript and should be the default choice when browser support allows it.
  • Recursive functions are concise and useful for explaining the underlying logic, but very deeply nested arrays can risk call stack errors.
  • Stackbased approaches avoid recursive calls, which can make them safer for unusually deep nesting.

When performance matters, I would recommend benchmarking with realistic data. Array size, nesting depth, JavaScript engine, and allocation patterns can all affect the final result.


Wrapping Up

Key Takeaways

  • The .flat() method (introduced in ES10) is the simplest way to flatten arrays.
  • Use .flat(Infinity) to flatten arrays with arbitrary nesting levels.
  • The custom helpers offer explicit deepflattening and sparsearray rules. Compile their TypeScript syntax and check runtime support before using them in older environments.
  • Choose the right method based on browser support requirements and data complexity.

Use flat() when its depth and sparsearray behaviour match the data you have. Use an explicit recursive or iterative implementation when you need different rules, support older runtimes, or must preserve information flat() discards. Decide the required depth before reaching automatically for Infinity.


Untangling a delivery problem?

Send the symptoms, constraints, and affected routes. I'll help identify whether the issue sits in the application, platform, content model, deployment path, or search surface.