# Array Flatten in JavaScript

A *nested array* is an array that contains other arrays as elements. Flattening means converting this multi-dimensional structure into a single-level array. In practice, flattening arrays is very useful –

for example,  
converting complex JSON data into a simple list for tables, charts or search indexes. It makes loops, filters, sorts, and searches much easier. In this guide, we’ll start with clear problem statements, use visual diagrams to explain nested structures, and walk through several methods (with code and complexity notes) to flatten arrays. We’ll cover built-in methods, recursion, iteration, generators, utility libraries, and discuss edge cases like sparse arrays and circular references.

## What Is a Nested Array?

A **nested array** is simply an array that contains one or more arrays inside it. Think of it like boxes inside boxes. For example:

```js
const nested = [1, [2, 3], [4, [5, 6]]];
```

Here, `nested[1]` is the array `[2, 3]`, and `nested[2]` is another array `[4, [5, 6]]` which itself contains the array `[5, 6]`. Visually, it looks like a tree:

```mermaid
graph LR
    A[[Root Array [1, [2,3], [4,[5,6]]]]] --> B[1]
    A --> C["[2,3]"]
    A --> D["[4,[5,6]]"]
    C --> E[2]
    C --> F[3]
    D --> G[4]
    D --> H["[5,6]"]
    H --> I[5]
    H --> J[6]
```

*Figure: A nested array (*`[1,[2,3],[4,[5,6]]]`*) shown as a tree – the root array has sub-arrays, which themselves contain values or further sub-arrays.*

By contrast, a **flat array** has no nested arrays: all elements are simple values (numbers, strings, objects, etc.). For example, the flattened version of the above would be `[1,2,3,4,5,6]`.

> **Definition:** *Flattening* a multi-dimensional array means converting it into a one-dimensional (flat) array by replacing each nested array with its contents.

This concept appears in many real-world situations. For instance, an API might return hierarchical categories, but your UI needs a flat list of tags. Or you may have nested comment threads that you want to process as a single list. Flattening is the operation that bridges these differences, turning a tree of data into a linear array.

## Why Flattening Arrays Is Useful

Flattening isn’t just a technical trick – it solves practical problems. As one developer notes, you often see *deeply nested data from an API* that must be converted into a flat list for use in your application. It also *normalizes data*: many libraries (for charts, tables, search indexes, etc.) expect flat data structures.  
For example:

*   **Data Processing:** Sorting, filtering, and searching are simpler on a 1D array than on nested structures.
    
*   **User Interfaces:** A UI might display a list of items (e.g. product tags), requiring flattening of nested categories.
    
*   **Algorithms:** Many algorithms work more efficiently on a simple list. For instance, removing duplicates or calculating aggregates is easier after flattening.
    
*   **Real Examples:** E-commerce sites often flatten product categories and sub-categories into tag lists. Analytics might flatten event hierarchies. Forms may flatten nested error messages into a single list.
    

In short, flattening “makes operations trivial” on a flat array. It’s a skill that separates a beginner (who just makes it work) from an experienced developer (who designs clean solutions).

## How Flattening Works (Core Concept)

Imagine flattening as a *tree traversal*. The **recursive idea** is:

> For each element in the array:
> 
> *   If it’s **not an array**, add it to the result.
>     
> *   If it *is* an array, flatten that array first (recursively), then add its contents to the result.
>     

This approach “beautifully mirrors how tree traversal works”. The result is a single list of all leaf values.

There are two main variations:

*   **Shallow flatten:** Only flatten one level deep (merge sub-arrays into root, but do not flatten deeper nested arrays).
    
*   **Deep flatten:** Flatten *all* levels (convert entire nested structure into one list).
    

Many algorithms iterate and apply this rule.  
For example, with our `nested = [1, [2, [3, [4]]], 5]`, a deep flatten yields `[1, 2, 3, 4, 5]`.

Here’s a simple **ASCII diagram** showing a flatten transformation:

```javascript
Before: [1, [2, [3, 4]], 5]
                 |
              [3,4]  (sub-array inside second element)
                 
Flatten each level:
    1. Inspect 1 → not an array → keep [1]
    2. Inspect [2,[3,4]] → is array → flatten it:
           - inspect 2 → [2]
           - inspect [3,4] → flatten to [3,4]
       combined result → [2, 3, 4]
    3. Inspect 5 → [5]
        
After: [1, 2, 3, 4, 5]
```

Or in a compact form:

```javascript
Nested: [1, [2, [3, 4]], 5]  
Flatten → [1, 2, 3, 4, 5]
```

This step-by-step reasoning – checking each element, handling numbers vs arrays – is key to flattening, and is exactly how we'd **explain this to an interviewer or beginner**.

## Approaches to Flatten Arrays (with Code)

There are many ways to flatten arrays in JavaScript. We’ll cover several approaches:

*   **Built-in method (**`flat`**) – ES2019+**
    
*   **Recursion (manual loop)**
    
*   **Functional (**`reduce` **+** `concat`**)**
    
*   **Iterative (stack or queue)**
    
*   **Generators**
    
*   **Utility libraries (lodash)**
    

Below is a summary of these approaches (code, time/space complexity, pros/cons):

| Method | Example Code Snippet | Time / Space | Pros | Cons |
| --- | --- | --- | --- | --- |
| **Array.prototype.flat** | `const flat = nestedArray.flat(Infinity);`【7†L214-L215】 | O(N) / O(N) | One-liner, optimized, depth param | Requires ES2019+ support |
| **Simple Recursion** | `js<br>function flatten(arr){<br> let res=[];<br> for(let x of arr){<br> if(Array.isArray(x)) res.push(...flatten(x));<br> else res.push(x);<br> }<br> return res;<br>}` | ≈O(N) / O(N) | Easy logic, handles any depth | Can hit call-stack on huge depth; intermediate arrays make it O(N log N) worst-case |
| `reduce` **+** `concat` | `js<br>const flatten = arr => arr.reduce((a, x) =><br> a.concat(Array.isArray(x) ? flatten(x) : x), []);`【13†L192-L195】 | ≈O(N) / O(N) | Concise, functional style | Similar recursion cons (stack overflow) |
| **Iterative (Stack)** | `js<br>function flatten(arr) {<br> let res=[], stack=[...arr];<br> while(stack.length){<br> let x = stack.pop();<br> if(Array.isArray(x)) stack.push(...x);<br> else res.unshift(x);<br> }<br> return res;<br>}`【13†L207-L215】 | O(N) / O(N) | No recursion (avoids call-stack limit) | Slightly more code; uses extra stack array |
| **Generator Function** | `js<br>function* flatGen(arr) {<br> for(let x of arr){<br> if(Array.isArray(x)) yield* flatGen(x);<br> else yield x;<br> }<br>}<br>const result = [...flatGen(nested)];` | O(N) / O(N) (for output) | Elegant lazy evaluation, re-usable iterator | Syntax complexity, less common |
| **Lodash** `_.flattenDeep` | `_.flattenDeep(nested);` (from Lodash)【28†L348-L355】 | O(N) / O(N) | One call, handles any depth | Requires including Lodash library |
| **Lodash** `_.flatten` | `_.flatten(nested);`【31†L348-L355】 | O(N) / O(N) | Flattens one level, easy | Only one level (not deep) |

*   *Time Complexity:* Generally each method touches each array element once, so **O(N)** where N is total number of elements across all levels. (However, note \[38\] that naive recursion with concatenation may degrade to *O(N log N)* in worst-case balanced nesting.
    
*   *Space Complexity:* All these methods create a new flat array, so **O(N)** extra space. Recursive or generator approaches also use O(depth) stack space.
    

Let’s look at these in more detail:

### 1\. Built-in `Array.prototype.flat()`

ES2019 introduced `flat()`, which **flattens an array** to a given depth【7†L214-L215】. For example:

```javascript
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat());       // [1, 2, 3, 4, [5, 6]]
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6]
```

*   By default, `flat()` flattens **one level** deep (like calling it with `1` depth). You can pass a depth parameter, even `Infinity` to flatten all the way.
    
*   It returns a **new array** and does not modify the original.
    
*   It also *removes empty slots* in sparse arrays. For example, `[1, , 3].flat()` yields `[1, 3]`. (Note: holes are removed, but values like `undefined` remain.
    
*   This method is highly optimized in modern engines and is usually the simplest solution for daily use.
    

**Pros:** Very concise, well-supported in modern environments, and optimized by the JS engine.  
**Cons:** Not available in very old JS environments (IE). Also, shallow vs deep must be set explicitly.

### 2\. Recursive Flattening (Loop + Recursion)

A classic interview solution is a recursive function:

```javascript
function flattenRecursive(arr) {
  let result = [];
  for (let item of arr) {
    if (Array.isArray(item)) {
      // Recurse into sub-array
      result = result.concat(flattenRecursive(item));
    } else {
      result.push(item);
    }
  }
  return result;
}

// Example:
console.log(flattenRecursive([1, [2, [3, 4]], 5])); // [1,2,3,4,5]
```

Here’s the step-by-step logic:

1.  Start with an empty `result` array.
    
2.  Loop through each element `item` in the input `arr`.
    
3.  If `item` is **not** an array, push it to `result`.
    
4.  If `item` *is* an array, call `flattenRecursive(item)` and concat its returned values into `result`.
    

This directly implements the “if array flatten it first” rule. It works for **any depth** of nesting.

**Pros:** Clear logic; directly shows the recursive thought process (often preferred in interviews).  
**Cons:** Can hit maximum call-stack for very deep arrays. Also, each recursive concat creates a new array, so in some pathological cases the runtime can be worse than linear (on the order of *O(N log N)* in a balanced nesting). In practice, for normal data this is usually fine.

> **Complexity:** Each element is processed once, so *O(N)* time in most cases. However, as explains, the use of `concat()` can introduce extra overhead (master theorem yields O(N log N) in the worst nested-case). Space is *O(N)* as we build a new array.

### 3\. Functional Style with `reduce()` + `concat()`

You can flatten in a functional style using `reduce()`:

```javascript
const flattenReduce = (arr) =>
  arr.reduce((acc, item) =>
    acc.concat(Array.isArray(item) ? flattenReduce(item) : item),
    []
  );

console.log(flattenReduce([1, [2, [3, 4]], 5])); // [1,2,3,4,5]
```

This approach is essentially the same recursion, but using `reduce` to accumulate results. It checks each `item`: if it’s an array, it recursively flattens it; otherwise it concatenates the item.

**Pros:** Elegant one-liner (aside from defining the function). Shows off functional programming knowledge.  
**Cons:** The underlying performance and recursion pitfalls are the same as the simple recursion above. Also, beginners may find the `reduce` callback syntax a bit tricky.

### 4\. Iterative Flatten (Using a Stack or Queue)

For very deep nesting, recursion might overflow the call stack. An iterative approach can avoid recursion entirely. One method uses a stack:

```javascript
function flattenIterative(arr) {
  const result = [];
  const stack = [...arr];  // copy initial array

  while (stack.length) {
    const next = stack.pop();
    if (Array.isArray(next)) {
      // Push all items of sub-array into stack
      stack.push(...next);
    } else {
      // Since we're popping from end, unshift to result front
      result.unshift(next);
    }
  }

  return result;
}

console.log(flattenIterative([1, [2, [3, 4]], 5])); // [1,2,3,4,5]
```

*   We copy the array into a `stack`.
    
*   While the stack is not empty, pop an element: if it’s an array, push its elements onto the stack; otherwise, insert it at the front of `result`.
    
*   In the end, `result` holds the flattened elements in original order. (We used `unshift`, which reverses order because we popped from the end. Alternatively, one could `push` and then `result.reverse()` at the end.)
    

**Pros:** No recursion, so you avoid call-stack limits. Good interview mention as a non-recursive alternative.  
**Cons:** Slightly more complex to write. Uses extra stack space.

### 5\. Generators (ES6 Iterators)

Another advanced method is to use a generator function. This can be useful if you want to lazily flatten:

```javascript
function* flattenGen(arr) {
  for (let x of arr) {
    if (Array.isArray(x)) {
      yield* flattenGen(x);
    } else {
      yield x;
    }
  }
}

const nested = [1, [2, [3, 4]], 5];
const flattenedArray = [...flattenGen(nested)];
console.log(flattenedArray); // [1,2,3,4,5]
```

The `flattenGen` function recursively yields values. We use `yield*` to yield values from a sub-generator when we encounter an array. Finally, we spread the generator into an array.

**Pros:** Clean, lazy evaluation. You can even process the flattened items one by one without building a full array.  
**Cons:** More complex syntax, and for many beginners this is advanced. The runtime behavior is similar to recursive flatten.

### 6\. Utility Libraries (Lodash)

If you’re using a utility library like [Lodash](https://lodash.com/), there are built-in methods:

*   `_.flatten` – flattens one level of array.
    
*   `_.flattenDeep` – flattens all levels (deep flatten).
    
*   `_.flattenDepth` – flattens to a specified depth.
    

For example:

```js
_.flatten([1, [2, [3]], 4]);       // [1, 2, [3], 4]
_.flattenDeep([1, [2, [3]], 4]);  // [1, 2, 3, 4]
```

**Pros:** Very convenient and well-tested.  
**Cons:** Requires loading Lodash, which may be overkill if that’s the only utility needed.

## Depth: Fixed vs Arbitrary

Some flatten functions allow specifying a *depth*. For instance, `flat(2)` only flattens two levels. The built-in `flat()` does this; Lodash’s `flattenDepth` does the same.

*   **Fixed depth:** Sometimes you only want to flatten a certain number of levels, not all the way. Example: `nestedArray.flat(1)` will only merge one level of sub-arrays.
    
*   **Arbitrary depth (full flatten):** Often you want *all* levels. Using `Infinity` with `flat()` (or `_.flattenDeep`) achieves this. Or simply write the recursive approach which naturally goes to any depth.
    

**Choosing depth** is often a requirement clarification in interviews: make sure you know whether to flatten one level or all levels. Always clarify this: for instance, ask “Should I flatten just one level or fully flatten?”.

## Order and Types: What Gets Preserved

Flattening preserves the original order of elements (albeit linearly) and does not change the elements themselves. For example, if there are `null` or `undefined` values, they remain (except that `flat()` removes *holes*). Sparse arrays (with “empty slots”) have those slots removed by `flat()`. Other types like objects or strings are just carried along if present.

**Example:** `[1, [2, null], undefined, [3, [4, []]]]` flattened (deep) becomes `[1, 2, null, undefined, 3, 4]`. Notice that the empty array `[]` just contributes no elements.

## Edge Cases

When flattening, watch out for these special scenarios:

*   **Empty or already-flat arrays:** If the input has no nested arrays, just return it (or a shallow copy) as-is.
    
*   **Negative or excessive depth (if using** `flat`**):** If someone passes an invalid depth (like negative), it’s common to treat it as 0 (no flatten) or Infinity. By default, `flat()` treats negative depth as 0 (no change).
    
*   **Sparse arrays:** `flat()` removes holes (see above. But manual methods (`forEach`, `for...of`) will *ignore* empty slots anyway, so this usually isn’t an issue unless you explicitly try to preserve length.
    
*   **Circular references:** If the nested structure refers to itself (e.g. `a = [1]; a.push(a);`), then naive recursive flattening will loop infinitely or crash. Most simple flatten functions do *not* handle circular structures – they will `RangeError: Maximum call stack` or run forever. To handle this, you’d need to track seen objects. Interview tip: **always clarify if circular references need handling**. Usually the task of flattening plain arrays assumes no circular refs.
    

In interviews, you might be asked about edge cases explicitly. For example, *“What if the array has no nesting?”* or *“How does your function handle* `[ [], [ ] ]` *or* `[null, [undefined, [ ]]]`*?”* It’s good to mention how your solution deals with empty sub-arrays (they just add nothing) and null/undefined (they stay as values, not ignored).

## Common Interview Scenario – “Flattening” Questions

A typical interview question is: **“Flatten an array of nested arrays into a single array”** without using the built-in `flat()`. They’ll look for your problem-solving approach and edge-case thinking. Steps to ace it:

1.  **Clarify Requirements:** Ask if it’s shallow vs deep flattening, and what to do with edge cases (empty arrays, non-arrays, etc.).
    
2.  **Start Simple:** Outline the recursive idea: “For each element, if it’s an array, flatten it recursively, else keep it.”
    
3.  **Code Iteratively:** Write the recursive or iterative code as shown above. Explain each step (loop through, check `Array.isArray()`).
    
4.  **Test with Examples:** Walk through a concrete example, like `[1, [2, [3, 4]], 5]`. Show how the recursion or loop handles each element.
    
5.  **Analyze Complexity:** Mention it’s roughly *O(N)* time (each element once) and *O(N)* space for the output. If asked, acknowledge the subtlety from \[38\] that naive concatenation can give O(N log N) in a perfectly balanced tree shape, whereas a single-pass approach can be strictly O(N).
    
6.  **Iterative Alternative:** If time permits, suggest an iterative stack solution to demonstrate depth understanding and avoid recursion depth issues.
    
7.  **Follow-ups:** Be prepared for follow-ups: *“What if elements aren’t numbers but objects?”* (You still keep them as-is). *“How do you prevent stack overflow on very deep arrays?”* (Use iterative or check depths). *“Could your solution modify the original array?”* (Typically no, we create a new array).
    

Interviewers may also ask variant questions, e.g. "Flatten only up to depth 2" or "Implement a Flatten Iterator (like LeetCode 341)". The key is showing you understand recursion vs iteration and can reason about data shape.

## Worked Examples

Let’s see some **concrete examples**, step-by-step.

1.  **One-Level Nesting:**
    
    *   **Input:** `[1, [2, 3], 4]`
        
    *   **Goal:** Flatten completely → `[1, 2, 3, 4]`.
        
    *   **Process:**
        
        1.  `1` is a number → keep `[1]`.
            
        2.  `[2, 3]` is an array → flatten it (one level): it yields `2, 3`. Append to result → `[1, 2, 3]`.
            
        3.  `4` is a number → append → `[1, 2, 3, 4]`.
            
    *   **Result:** `[1, 2, 3, 4]`.
        
    *   (Using `flat(Infinity)` or recursion gives the same result.)
        
2.  **Two-Level Nesting:**
    
    *   **Input:** `[1, [2, [3, 4], 5], 6]`
        
    *   **Process:**
        
        1.  `1` → `[1]`.
            
        2.  `[2, [3, 4], 5]` → array, flatten it:
            
            *   `2` → add `2` → `[1, 2]`.
                
            *   `[3, 4]` → flatten it: `3, 4` → add → `[1, 2, 3, 4]`.
                
            *   `5` → add → `[1, 2, 3, 4, 5]`.
                
        3.  `6` → add → `[1, 2, 3, 4, 5, 6]`.
            
    *   **Result:** `[1, 2, 3, 4, 5, 6]`.
        
3.  **Mixed Types & Empty Arrays:**
    
    *   **Input:** `[0, [], [1, [2, []], null], undefined]`
        
    *   **Goal:** Flatten completely.
        
    *   **Process:**
        
        1.  `0` → `[0]`.
            
        2.  `[]` (empty array) → flatten (yields nothing) → still `[0]`.
            
        3.  `[1, [2, []], null]`:
            
            *   `1` → add → `[0, 1]`.
                
            *   `[2, []]`:
                
                *   `2` → add → `[0,1,2]`.
                    
                *   `[]` → yields nothing.
                    
            *   `null` → add → `[0, 1, 2, null]`.
                
        4.  `undefined` → add → `[0, 1, 2, null, undefined]`.
            
    *   **Result:** `[0, 1, 2, null, undefined]`.
        

Each step we **keep order**. Note that holes (the empty arrays `[]`) just add no elements, and `null`/`undefined` remain in the output.

*Walkthrough Example (Recursion):* For `[1, [2, [3, 4], 5], 6]` with recursion:

```javascript
flatten([1, [2, [3,4], 5], 6])
= [1].concat(flatten([2, [3,4], 5])).concat([6])
flatten([2, [3,4], 5])
= [2].concat(flatten([3,4])).concat([5])
flatten([3,4])
= [3, 4]  // base case (no arrays inside)
=> Combine: [2].concat([3,4]).concat([5]) = [2,3,4,5]
=> Combine: [1].concat([2,3,4,5]).concat([6]) = [1,2,3,4,5,6]
```

## Practice Problems

Try these on your own and check the answers:

1.  **Problem:** Flatten `[[1, 2], 3, [4, [5]], 6]`.  
    **Answer:** `[1, 2, 3, 4, 5, 6]`.  
    *Explanation:* Merge inner arrays: `[1,2]` → `1,2`; `[4,[5]]` → `4`, flatten `[5]` → `5`.
    
2.  **Problem:** Write a function `flatten(arr)` that only flattens one level (shallow flatten). Example: `[1, [2, [3]], 4]` → `[1, 2, [3], 4]`. What does `flatten([1, [2, [3]], 4])` return?  
    **Answer:** `[1, 2, [3], 4]` (only the first `[` is flattened).  
    *Explanation:* One-level flatten just removes one set of brackets.
    
3.  **Problem:** Given `const data = [1, [2, [3, [4, [5, 6]]]], 7]`, what does `data.flat(2)` and `data.flat(Infinity)` produce?  
    **Answer:**
    
    *   `data.flat(2)` → `[1, 2, 3, [4, [5,6]], 7]` (only flattens two levels).
        
    *   `data.flat(Infinity)` → `[1, 2, 3, 4, 5, 6, 7]` (completely flat).  
        *Explanation:* `flat(2)` merges the first two levels of nesting but leaves the deeper `[4,[5,6]]` partially nested.
        
4.  **Problem:** (Edge case) What happens with an empty array or no nesting? Flatten `[]` or `[1,2,3]`.  
    **Answer:** Flattening yields the same array (or a copy). e.g. `[].flat()` → `[]`; `[1,2,3].flat()` → `[1,2,3]`.  
    *Explanation:* There’s nothing to flatten.
    
5.  **Problem:** (Tricky) Given a sparse array like `[1, , [3, ,4], 5]`, what does `flat()` do?  
    **Answer:** It removes holes: `[1, 3, 4, 5]`【36†L300-L308】.  
    *Explanation:* The empty slots are removed by `flat()`【36†L300-L308】.
    

If you’re studying, try writing the flatten function yourself for some practice cases like these.

## Summary and Best Practices

*   **Understand the problem:** A nested array is just an array containing arrays. Flattening means turning it into one flat array.
    
*   **Use built-ins when possible:** `arr.flat(depth)` is concise and efficient. For older environments, you can polyfill or use a library like Lodash (`_.flattenDeep`).
    
*   **Know multiple approaches:** Recursion is straightforward and often expected. Iterative or generator methods show deep understanding. Each approach has its trade-offs (stack depth vs code length).
    
*   **Think about edge cases:** Explain how your code handles empty arrays, holes, non-array elements, and circular references.
    
*   **Maintain readability:** Even if using recursion or advanced methods, write clean code with clear variable names. Commenting the logic (as we did above) helps beginners and interviewers follow your thought process.
    
*   **Complexity matters:** Be prepared to analyze your solution. Simple flatten is typically *O(N)* time and space. If concatenating arrays in recursion, mention the caveat that it can become *O(N log N)* in balanced cases, and suggest improvements if needed.
    
*   **Use diagrams and examples:** As we’ve shown, drawing a quick sketch of the nested array or writing a small example can make the flattening process crystal clear.
    

By following these guidelines and trying out the approaches above, beginners will not only flatten arrays but also understand **why** each step works. Practice writing out the steps or diagrams for new examples to solidify the concept. Happy coding!
