Flattening Nested Arrays

Medium100% Free~25 mins#array#flatten#recursion#stack#sparse-arrays#algorithms#polyfill
Key Learning Objectives
✓

Implement recursive array flattening with customizable depth parameters.

✓

Implement iterative array flattening using an explicit stack to prevent call stack overflow.

✓

Master native Array.prototype.flat() semantics: default depth of 1, Infinity for complete flattening.

✓

Understand sparse array handling: why flat() removes uninitialized empty slots (holes).

✓

Analyze time and space complexity tradeoffs across recursive and iterative flattening algorithms.

The Interview Problem

What is logged to the console when Array.prototype.flat() is executed on deeply nested and sparse arrays, and how does the native flattening algorithm handle uninitialized empty slots?

1const nested = [1, [2, [3, [4]]]];
2const res1 = nested.flat(1);
3const res2 = nested.flat(Infinity);
4
5const sparse = [1, , 3];
6const res3 = sparse.flat();
7
8console.log(
9 res1.length,
10 res2.join('-'),
11 res3.length,
12 1 in sparse
13);
Predict Console Output
Interactive Challenge

Select the option that matches what standard ECMAScript prints to the console:

3 1-2-3-4 2 false

4 1-2-3-4 3 true

3 1-2-3-4 3 false

2 1-2-3-4 2 true

V8 Engine Execution Trace
Step 1 of 6 (Line 1)

Allocates nested array hierarchy in heap memory.

Call Stack (Top = Active)
Global Execution Context
Lexical Scope / Bindings
nested:[1, [2, [3, [4]]]]
Console Stream
> [empty]

Deep Technical Breakdown

Implementing Array Flattening in JavaScript

Flattening arrays is a standard FAANG interview problem with two principal implementation patterns:

1. Recursive Implementation with Depth Control

javascript
function flatten(arr, depth = 1) {
  if (depth <= 0) return arr.slice();
  const result = [];
  for (const item of arr) {
    if (Array.isArray(item)) {
      result.push(...flatten(item, depth - 1));
    } else {
      result.push(item);
    }
  }
  return result;
}
  • Time Complexity: $O(N)$ where $N$ is the total count of elements across all nested arrays.
  • Space Complexity: $O(D)$ where $D$ is the maximum recursion depth, plus $O(N)$ for the output array.
  • Limitation: Extremely deep recursion (e.g., $D > 10,000$) will trigger RangeError: Maximum call stack size exceeded.

2. Iterative Implementation Using an Explicit Stack

To eliminate call stack limitations, manage state using an in-memory stack:

javascript
function flattenIterative(arr) {
  const stack = [...arr];
  const result = [];
  while (stack.length > 0) {
    const next = stack.pop();
    if (Array.isArray(next)) {
      stack.push(...next);
    } else {
      result.push(next);
    }
  }
  return result.reverse();
}

The Sparse Array Trap

A sparse array contains indices that have never been initialized ([1, , 3]). This is fundamentally different from [1, undefined, 3]:

  • 1 in [1, , 3] is false (no property key '1' exists on the array).
  • 1 in [1, undefined, 3] is true.
  • The ECMAScript specification (§ 23.1.3.11) mandates that Array.prototype.flat discards empty slots entirely. [1, , 3].flat() produces [1, 3].
Common Traps & Mistakes

Assuming `flat()` defaults to flattening all levels. Its default depth is `1`. To flatten completely, pass `Infinity`.

Using naive recursion without depth control or tail call optimization on untrusted deeply nested inputs.

Confusing unallocated sparse array holes (`[1, , 2]`) with `undefined` values (`[1, undefined, 2]`).

Using `toString()` or `arr.join(',')` to flatten numeric arrays. This destroys nested empty arrays (`[]`), alters strings with commas, and converts types.

FAANG Follow-Up Probes
Probe #1

How does Array.prototype.flatMap() differ from calling .map() followed by .flat() in terms of performance and depth?

Probe #2

How would you implement a generator-based lazy flattener (function* flatGen(arr)) to stream elements without loading the entire flattened array in memory?

Probe #3

How does V8 optimize array iteration when encountering packed arrays versus holey (sparse) arrays?