Skip to main content

Command Palette

Search for a command to run...

Flattening Arrays in JavaScript

Updated
•10 min read•View as Markdown
Flattening Arrays in JavaScript

Arrays are one of the first things you learn in JavaScript. They are straightforward until the moment someone shows you an array that lives inside another array, which lives inside yet another array. That structure has a name: a nested array. And at some point, you will need to flatten it.

This post walks you through what nested arrays are, why flattening them is useful, and every practical way to do it in JavaScript, with enough detail that you genuinely understand the reasoning behind each approach, not just the syntax.


What is a nested array?

A nested array is simply an array that contains one or more arrays as its elements. Those inner arrays can themselves contain more arrays, and so on. There is no hard limit to how deep this can go.

// A flat array — all values sit at the same level
const scores = [10, 20, 30, 40];

// A nested array — some values are arrays themselves
const nestedScores = [10, [20, 30], [40, [50, 60]]];

The diagram above shows how [1, 2, 3], [4, [5, 6]], [7, 8] relates to its parent and its deeply nested child. The top-level array at level 0 contains three elements, one of which goes another level deeper.

You will run into nested arrays more often than you might expect. APIs sometimes return data in chunks grouped as arrays. Functions that process multiple lists can produce nested results. Category trees and grouped datasets almost always come nested.


Why would you ever need to flatten an array?

Most of the operations you perform on arrays — map, filter, forEach, reduce — work on the elements at the current level. They do not automatically dig into nested structures.

const roster = [
  ["Virat Kohli", "Rohit Sharma"],
  ["Jasprit Bumrah", "Hardik Pandya"],
  ["KL Rahul"]
];

// You want every player's name in a single list
// But this gives you three arrays, not individual names
console.log(roster.length); // 3 (three sub-arrays, not five players)

roster.forEach(player => console.log(player));
// ["Virat Kohli", "Rohit Sharma"]
// ["Jasprit Bumrah", "Hardik Pandya"]
// ["KL Rahul"]

If you want to loop over each player, count them, or render them as a list in a UI, you need one flat array of strings, not an array of arrays. That is where flattening comes in.


What does flattening actually mean?

Flattening means taking a nested array and pulling all the values up to a single level. You are collapsing the structure so that all the elements sit side by side instead of being buried inside each other.

Here is a visual way to think about it:

Before flattening:
[1, [2, 3], [4, [5, 6]]]

After flattening one level:
[1, 2, 3, 4, [5, 6]]     ← the inner [5, 6] is still nested

After flattening all levels (deep flatten):
[1, 2, 3, 4, 5, 6]       ← everything is at the surface now

The key idea is depth. A shallow flatten only removes one layer of nesting. A deep flatten removes all layers, no matter how many there are.


The four ways to flatten arrays in JavaScript### 1. Using Array.prototype.flat()

This is the most direct way. The flat() method was added in ES2019, and it is exactly what it sounds like: call it on an array, and it flattens it.

const players = [
  "Virat Kohli",
  ["Rohit Sharma", "Shubman Gill"],
  ["Jasprit Bumrah", ["Hardik Pandya", "KL Rahul"]]
];

// Flatten one level deep (the default)
console.log(players.flat());
// ["Virat Kohli", "Rohit Sharma", "Shubman Gill", "Jasprit Bumrah", ["Hardik Pandya", "KL Rahul"]]
// Notice that ["Hardik Pandya", "KL Rahul"] is still nested — only one level was removed

// Flatten two levels deep
console.log(players.flat(2));
// ["Virat Kohli", "Rohit Sharma", "Shubman Gill", "Jasprit Bumrah", "Hardik Pandya", "KL Rahul"]

// Flatten all levels, no matter how deep
console.log(players.flat(Infinity));
// ["Virat Kohli", "Rohit Sharma", "Shubman Gill", "Jasprit Bumrah", "Hardik Pandya", "KL Rahul"]

When to use it: For most real-world cases where you know the depth, or when you want to flatten completely and pass Infinity. Clean, readable, and built into the language.

One thing flat() does quietly that many people overlook: it removes empty slots from sparse arrays.

const sparse = [1, , 3, [4, , 6]];
console.log(sparse.flat());
// [1, 3, 4, 6]  — the empty slots are gone

2. Using flatMap()

flatMap() combines map() and a single level of flat() in one step. You use it when you want to transform each element and the transformation produces arrays that you immediately want flattened.

const cities = ["Mumbai", "Delhi", "Bangalore"];

// map() alone gives you an array of arrays
const withMap = cities.map(city => [city, city.toUpperCase()]);
console.log(withMap);
// [["Mumbai", "MUMBAI"], ["Delhi", "DELHI"], ["Bangalore", "BANGALORE"]]

// flatMap() maps and flattens in one pass
const withFlatMap = cities.flatMap(city => [city, city.toUpperCase()]);
console.log(withFlatMap);
// ["Mumbai", "MUMBAI", "Delhi", "DELHI", "Bangalore", "BANGALORE"]

A practical example: you have a list of orders, and each order has multiple items. You want a flat list of all items.

const orders = [
  { id: 1, items: ["bat", "gloves"] },
  { id: 2, items: ["jersey", "cap", "shoes"] },
  { id: 3, items: ["pad"] }
];

const allItems = orders.flatMap(order => order.items);
console.log(allItems);
// ["bat", "gloves", "jersey", "cap", "shoes", "pad"]

Important: flatMap() only flattens one level. If the arrays produced by your transformation are themselves nested, those deeper layers stay nested.


3. Using reduce()

Before flat() was available, reduce() was the go-to approach. It is worth understanding because you will see it in older codebases and because knowing how it works builds good intuition about the underlying problem.

The idea is to walk through every element in the array and concatenate it onto an accumulator. When the element is itself an array, concat() spreads it into the accumulator automatically.

const scores = [[80, 92], [75, 88, 94], [60]];

const flattened = scores.reduce((accumulator, current) => {
  return accumulator.concat(current);
}, []);

console.log(flattened);
// [80, 92, 75, 88, 94, 60]

Step by step, here is what happens:

Start:      accumulator = []
Iteration 1: current = [80, 92]     → [].concat([80, 92])       → [80, 92]
Iteration 2: current = [75, 88, 94] → [80, 92].concat([75, 88, 94]) → [80, 92, 75, 88, 94]
Iteration 3: current = [60]         → [...].concat([60])          → [80, 92, 75, 88, 94, 60]

This handles one level of nesting. For deeper nesting with reduce(), you need recursion inside the reducer, which is covered in the next section.


4. Recursion (manual flattening)

Recursion is the technique where a function calls itself. For flattening, this is the most powerful approach because it handles any depth automatically, regardless of how deeply things are nested.

The logic is: go through each element. If the element is an array, flatten it (call yourself on it). If it is not an array, add it to the result as-is.

function flattenDeep(arr) {
  const result = [];

  for (const item of arr) {
    if (Array.isArray(item)) {
      // item is an array — go deeper
      const nested = flattenDeep(item);
      result.push(...nested);
    } else {
      // item is a plain value — collect it
      result.push(item);
    }
  }

  return result;
}

const deeply = [1, [2, [3, [4, [5]]]]];
console.log(flattenDeep(deeply));
// [1, 2, 3, 4, 5]

Let us trace through a smaller example to see the recursion unfold:

flattenDeep([1, [2, [3]]])

// Loop: item = 1 → not an array → push 1
// Loop: item = [2, [3]] → is an array → call flattenDeep([2, [3]])
//   Loop: item = 2 → not an array → push 2
//   Loop: item = [3] → is an array → call flattenDeep([3])
//     Loop: item = 3 → not an array → push 3
//     return [3]
//   Spread [3] into result → result = [2, 3]
//   return [2, 3]
// Spread [2, 3] into result → result = [1, 2, 3]
// return [1, 2, 3]

A more concise version using reduce() together with recursion:

function flattenDeep(arr) {
  return arr.reduce((acc, item) => {
    return Array.isArray(item)
      ? acc.concat(flattenDeep(item))
      : acc.concat(item);
  }, []);
}

const cityGroups = ["Mumbai", ["Delhi", ["Bangalore", ["Chennai"]]]];
console.log(flattenDeep(cityGroups));
// ["Mumbai", "Delhi", "Bangalore", "Chennai"]

When recursion matters: If you are working with data where the depth is unknown and could vary from one dataset to the next, recursion is the right choice. It is also what flat(Infinity) is doing internally, in a sense.


Comparing the four approaches

Here is a quick summary of when to reach for each one:

Approach Handles any depth? Returns new array? When to use
flat(n) Yes (pass Infinity) Yes Most common case, modern JS
flatMap() One level only Yes When transforming and flattening together
reduce() One level (without recursion) Yes Older codebases, custom logic
Recursion Yes, always Yes Unknown depth, full control needed

A few things to keep in mind

flat() and flatMap() do not modify the original array. They return a new one. Your original data stays untouched, which is the behavior you almost always want.

const original = [1, [2, 3]];
const flat = original.flat();

console.log(original); // [1, [2, 3]] — unchanged
console.log(flat);     // [1, 2, 3]

Also, be cautious with very deeply nested arrays and recursion. If the nesting goes thousands of levels deep, JavaScript's call stack has limits and you may hit a stack overflow error. In practice this is rare, but it is worth knowing.


Putting it all together

Here is a practical scenario. You are building a cricket stats app. Teams are grouped by division, and players are nested inside their teams. You want a single flat list of all players.

const divisions = [
  {
    division: "North",
    teams: [
      { name: "Delhi Capitals", players: ["Rishabh Pant", "Axar Patel"] },
      { name: "Punjab Kings", players: ["Shikhar Dhawan", "Arshdeep Singh"] }
    ]
  },
  {
    division: "South",
    teams: [
      { name: "Chennai Super Kings", players: ["MS Dhoni", "Ravindra Jadeja"] },
      { name: "Royal Challengers", players: ["Virat Kohli", "Faf du Plessis"] }
    ]
  }
];

// Step 1: get all teams from all divisions
const allTeams = divisions.flatMap(division => division.teams);

// Step 2: get all players from all teams
const allPlayers = allTeams.flatMap(team => team.players);

console.log(allPlayers);
// ["Rishabh Pant", "Axar Patel", "Shikhar Dhawan", "Arshdeep Singh",
//  "MS Dhoni", "Ravindra Jadeja", "Virat Kohli", "Faf du Plessis"]

You could also combine both steps into one:

const allPlayers = divisions.flatMap(division =>
  division.teams.flatMap(team => team.players)
);

Clean, readable, and no mutation.


Wrapping up

Nested arrays show up constantly in real JavaScript work, especially when dealing with APIs, grouped datasets, and multi-step transformations. Knowing how to flatten them, and which tool to use depending on the situation, saves you from a lot of unnecessary looping and confusion.

To summarize: reach for flat() when you need a quick flatten with known depth. Use flatMap() when you are transforming and flattening in the same step. Fall back to reduce() if you need fine-grained control or are working without modern JS support. And write a recursive function when depth is unpredictable and you want complete control over the process.

Once this clicks, you will start seeing nested arrays not as something messy to deal with, but as structured data waiting to be shaped.

More from this blog