Skip to main content

Command Palette

Search for a command to run...

Array Flatten in JavaScript

Updated
6 min read
Array Flatten in JavaScript

In programming, an array of arrays (also called a nested or multidimensional array) is a structure where each element of a main array is itself another array.

Think as you want to open a xyz file, you have to open folder, and inside it you have to open another folder, then you can access the file.

Similarly, the nested Array [[1,2] , [3,4]] is the same, if you want to find or traverse through the entire element in the array, you have to use multiple loops. As deep the array are that much loop will be used.

And it will slow down the performance when you have large number of users.

What nested arrays are

It is essentially a box containing smaller boxes. For example, in a 2D array like [[1, 2], [3, 4]], the outer array holds two inner arrays. These are commonly used to represent:

  • Grids or Matrices: Such as a game board, a spreadsheet, or image pixels.

  • Grouped Data: Storing marks by subject, products by category, or cities by state.

Accessing deeply nested values requires multiple loops (e.g., a loop inside a loop), making code harder to read and maintain.

Operations like searching or removing duplicates often lead to quadratic time complexity (O(N^2)), which can significantly slow down large datasets compared to a linear (O(N))operation on a flat list.

Why flattening arrays is useful

Flattening is the process of converting a nested structure into a single, simple list (e.g., [[1, 2], [3]] becomes [1, 2, 3]).

  • Simplified Processing: Most standard array methods (like filter, sort, or map) and UI libraries (like Chart.js) work most efficiently with linear, one-dimensional data.

  • Easier Searching: Finding a specific value is much simpler when you don't have to navigate multiple levels of "folders".

  • Data Normalization: When fetching data from different APIs, the results might come in varied nested formats. Flattening "normalizes" this data into a consistent format that your program can handle predictably.

  • Performance Optimization: In some low-level scenarios (like 3D game engines), using a single flat array instead of a 3D one reduces memory lookups and can improve speed by up to 20% by utilizing cache locality.

Concept of flattening arrays

Flattening is the process of converting a multi-dimensional or nested array into a single, one-dimensional array. It essentially "opens up" all the inner boxes (sub-arrays) and places their individual elements into one continuous list, preserving their original relative order.

Different approaches to flatten arrays

Built-in Methods (JavaScript)

The simplest way is to use the functions provided by the language itself.

  • JavaScript flat(): Use Infinity to flatten all levels regardless of depth

    const nested = [1, [2, [3, 4], 5], 6];
    const simpleFlat = nested.flat(); // [1, 2, [3, 4], 5, 6] (default depth 1)
    const deepFlat = nested.flat(Infinity); // [1, 2, 3, 4, 5, 6]
    
  • Recursive Approach (Manual Implementation)
    This method uses a function that calls itself whenever it finds another array.

    function flattenRecursive(arr) {
      let result = [];
      arr.forEach(item => {
        if (Array.isArray(item)) {
          result = result.concat(flattenRecursive(item));
        } else {
          result.push(item);
        }
      });
      return result;
    }
    
  • Functional Programming (reduce + concat)

    A concise way to flatten exactly one level at a time using high-order functions.

    const nested = [1, [2, 3], [4, 5]];
    const flat = nested.reduce((acc, val) => acc.concat(val), []);
    // Result: [1, 2, 3, 4, 5]
    

Common interview scenarios

The "Polyfill" Style (Flatten to specific depth)

Often, the interviewer will ask: "Can you make it work like Array.prototype.flat(n)?"

function flattenToDepth(arr, depth = 1) {
  // Base case: if depth is 0, return the array as is
  if (depth === 0) return arr;

  return arr.reduce((acc, val) => {
    if (Array.isArray(val)) {
      // Recursively flatten and decrement depth
      acc.push(...flattenToDepth(val, depth - 1));
    } else {
      acc.push(val);
    }
    return acc;
  }, []);
}

// Test
const deeplyNested = [1, [2, [3, [4]]]];
console.log(flattenToDepth(deeplyNested, 2)); // [1, 2, 3, [4]]

Common Coding Challenges

  • Implement a Polyfill: Interviewers often ask you to write your own version of Array.prototype.flat() from scratch without using the built-in method.

  • Flatten to a Specific Depth: You might be given an input array and a depth, and asked to only flatten sub-arrays up to that level.

  • Recursive vs. Iterative: A common follow-up is to provide a recursive solution and then ask for an iterative one using a stack to avoid potential "stack overflow" errors with deeply nested data.

// Adding a custom myFlat function to the Array prototype
Array.prototype.myFlat = function(depth = 1) {
  // If depth is less than 1, just return a shallow copy
  if (depth < 1) return this.slice();

  return this.reduce((acc, val) => {
    if (Array.isArray(val)) {
      // If element is an array, flatten it and decrease depth counter
      acc.push(...val.myFlat(depth - 1));
    } else {
      // If it's a value, just push it
      acc.push(val);
    }
    return acc;
  }, []);
};

// Usage:
const nested = [1, [2, [3, [4]]]];
console.log(nested.myFlat(2)); // [1, 2, 3, [4]]

DRY RUN
Initial State

  • Target: nested.myFlat(2)

  • Array: [1, [2, [3, [4]]]]

  • Depth: 2

Level 1 (The Main Array)

Processing [1, [2, [3, [4]]]]

  • Element 1: Not an array. Push to acc.

    • acc = [1]
  • Element [2, [3, [4]]]: Is an array? Yes. Depth (2) > 0? Yes.

    • Action: Recurse. Call [2, [3, [4]]].myFlat(1)

Level 2 (The First Sub-array)

Processing [2, [3, [4]]]

  • Element 2: Not an array. Push to acc.

    • acc = [2]
  • Element [3, [4]]: Is an array? Yes. Depth (1) > 0? Yes.

    • Action: Recurse. Call [3, [4]].myFlat(0)

Level 3 (The Second Sub-array)

Processing [3, [4]]

  • Check: Is depth < 1? Yes (depth is 0).

  • Action: if (depth < 1) return this.slice();

  • Return: Returns a shallow copy of the current array: [3, [4]].

Backtracking (Merging the Results)

  1. Back in Level 2:

    • The recursive call returned [3, [4]].

    • The code executes acc.push(...[3, [4]]).

    • acc becomes [2, 3, [4]].

    • Return: Returns [2, 3, [4]] to Level 1.

  2. Back in Level 1:

    • The recursive call returned [2, 3, [4]].

    • The code executes acc.push(...[2, 3, [4]]). (Remember, Level 1's acc already had 1).

    • acc becomes [1, 2, 3, [4]].

    • Return: Final result.

Final Output

[1, 2, 3, [4]]

Critical Interview Scenarios & Edge Cases

When solving this in an interview, you should proactively address these constraints and edge cases to show depth:

  • Heterogeneous Data: Handling arrays that contain numbers, strings, null, or undefined without breaking.

  • Empty Arrays: Ensuring your logic doesn't fail when it encounters [] as a nested element.

  • Immutability: Clarifying if the function should return a new array or modify the original (usually, a new array is preferred).

  • Circular References: A "senior level" challenge might ask how you would handle an array that contains a reference to itself, which could cause infinite recursion.

Happy Coding!