Episode 1 — Fundamentals / 1.22 — Array Methods

1.22.h — flat() and flatMap()

In one sentence: flat(depth) flattens nested arrays down to a specified depth, and flatMap() combines a map followed by flat(1) in a single, more efficient step.

Navigation: ← 1.22.g — sort() · 1.22.i — Functional Thinking →


1. flat(depth) — flatten nested arrays

flat() creates a new array with all sub-array elements concatenated up to the specified depth.

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

console.log(flat);   // [1, 2, 3, 4, 5, 6]
console.log(nested); // [[1, 2], [3, 4], [5, 6]]  ← unchanged

Syntax

const newArray = arr.flat(depth);
ParameterDescriptionDefault
depthHow many levels of nesting to flatten1
ReturnNew flattened array
Mutates?No

2. Default depth is 1

flat() with no argument only flattens one level:

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

deep.flat();    // [1, 2, [3, [4]]]     — one level flattened
deep.flat(1);   // [1, 2, [3, [4]]]     — same as above
deep.flat(2);   // [1, 2, 3, [4]]       — two levels
deep.flat(3);   // [1, 2, 3, 4]         — three levels

3. flat(Infinity) — flatten all levels

When you do not know the nesting depth, use Infinity:

const crazy = [1, [2, [3, [4, [5, [6]]]]]];

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

This is safe and commonly used. It will flatten any depth.


4. flat removes holes (sparse arrays)

A bonus behavior: flat removes empty slots:

const sparse = [1, , 3, , 5];
console.log(sparse.flat()); // [1, 3, 5]  — holes removed

const nested = [1, , [3, , 5]];
console.log(nested.flat()); // [1, 3, 5]  — holes at both levels removed

5. flatMap() — map + flat(1) in one step

flatMap first maps each element using a callback, then flattens the result by one level. It is more efficient than calling .map(...).flat() separately because it does both in a single pass.

const sentences = ["Hello world", "How are you"];

// map then flat
const words1 = sentences.map(s => s.split(" ")).flat();
// [["Hello", "world"], ["How", "are", "you"]].flat()
// ["Hello", "world", "How", "are", "you"]

// flatMap — same result, one step
const words2 = sentences.flatMap(s => s.split(" "));
// ["Hello", "world", "How", "are", "you"]

Syntax

const newArray = arr.flatMap(callback(element, index, array));
FeatureflatMap
Maps each elementYes
Flattens resultOne level only
ReturnsNew array
Mutates?No

6. flatMap for filtering and mapping simultaneously

flatMap can add or remove elements by returning arrays of different lengths:

// Return empty array to remove, single-element array to keep, multi-element to expand
const nums = [1, 2, 3, 4, 5, 6];

const result = nums.flatMap(n => {
  if (n % 2 === 0) return [n * 10];  // keep and transform evens
  return [];                          // remove odds
});

console.log(result); // [20, 40, 60]

This is equivalent to .filter(...).map(...) but in a single pass:

// Equivalent:
nums.filter(n => n % 2 === 0).map(n => n * 10); // [20, 40, 60]

Expand elements (one-to-many)

const pairs = [1, 2, 3];
const expanded = pairs.flatMap(n => [n, n * n]);
console.log(expanded); // [1, 1, 2, 4, 3, 9]

7. Use cases

Normalize nested categories

const categories = [
  { name: "Electronics", subcategories: ["Phones", "Laptops", "Tablets"] },
  { name: "Clothing", subcategories: ["Shirts", "Pants"] },
  { name: "Books", subcategories: ["Fiction", "Non-fiction", "Comics"] },
];

// Get all subcategories in a flat list
const allSubs = categories.flatMap(cat => cat.subcategories);
console.log(allSubs);
// ["Phones", "Laptops", "Tablets", "Shirts", "Pants", "Fiction", "Non-fiction", "Comics"]

// Or with category prefix
const labeled = categories.flatMap(cat =>
  cat.subcategories.map(sub => `${cat.name} > ${sub}`)
);
console.log(labeled);
// ["Electronics > Phones", "Electronics > Laptops", ...]

Merge nested API results

const pages = [
  { page: 1, results: [{ id: 1 }, { id: 2 }] },
  { page: 2, results: [{ id: 3 }, { id: 4 }] },
  { page: 3, results: [{ id: 5 }] },
];

const allResults = pages.flatMap(p => p.results);
console.log(allResults);
// [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]

Split and flatten strings

const csvRows = ["a,b,c", "d,e,f", "g,h,i"];
const allCells = csvRows.flatMap(row => row.split(","));
console.log(allCells);
// ["a", "b", "c", "d", "e", "f", "g", "h", "i"]

Duplicate with variation

const colors = ["red", "blue", "green"];

const withShades = colors.flatMap(color => [
  `light-${color}`,
  color,
  `dark-${color}`,
]);

console.log(withShades);
// ["light-red", "red", "dark-red", "light-blue", "blue", "dark-blue", ...]

8. flat vs manual flattening

Before flat existed (ES2019)

// Using reduce + concat
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);

// Using spread in reduce
const flat2 = nested.reduce((acc, arr) => [...acc, ...arr], []);

// Deep flatten with recursion
function deepFlat(arr) {
  return arr.reduce((acc, val) =>
    Array.isArray(val) ? acc.concat(deepFlat(val)) : acc.concat(val),
    []
  );
}

Now — just use flat

nested.flat();           // one level
nested.flat(Infinity);   // all levels

9. flatMap only flattens one level

Important limitation: flatMap is equivalent to map + flat(1), not map + flat(Infinity):

const arr = [1, 2, 3];

const result = arr.flatMap(n => [[n, n * 2]]);
console.log(result); // [[1, 2], [2, 4], [3, 6]]
// The inner arrays remain — only one level was flattened

// If you need deeper flattening:
arr.map(n => [[n, n * 2]]).flat(2);
// [1, 2, 2, 4, 3, 6]

10. Processing tree-like data

const fileSystem = [
  {
    name: "src",
    children: [
      { name: "index.js", children: [] },
      {
        name: "components",
        children: [
          { name: "App.js", children: [] },
          { name: "Header.js", children: [] },
        ],
      },
    ],
  },
  { name: "package.json", children: [] },
];

// Get all file/folder names (one level deep)
const topLevel = fileSystem.flatMap(item => [
  item.name,
  ...item.children.map(c => `  ${c.name}`),
]);

console.log(topLevel);
// ["src", "  index.js", "  components", "package.json"]

// For full recursive traversal, use a recursive function with flat:
function getAllNames(items, depth = 0) {
  return items.flatMap(item => [
    "  ".repeat(depth) + item.name,
    ...getAllNames(item.children, depth + 1),
  ]);
}

console.log(getAllNames(fileSystem));
// ["src", "  index.js", "  components", "    App.js", "    Header.js", "package.json"]

11. Performance considerations

  • flat(1) is well-optimized in modern engines.
  • flat(Infinity) on deeply nested structures is fine for normal use but avoid in hot paths with massive data.
  • flatMap is faster than .map().flat() because it avoids creating the intermediate array.

12. Edge cases

// Empty array
[].flat();       // []
[].flatMap(x => [x]); // []

// Already flat
[1, 2, 3].flat(); // [1, 2, 3] — new array, same values

// Mixed nesting
[1, [2, 3], 4, [5]].flat(); // [1, 2, 3, 4, 5]

// Non-array elements remain
[1, "hello", [2, 3]].flat(); // [1, "hello", 2, 3]

Key takeaways

  1. flat(depth) flattens nested arrays to the specified depth — default is 1.
  2. flat(Infinity) flattens all nesting levels.
  3. flat also removes holes in sparse arrays.
  4. flatMap(fn) = map(fn) + flat(1) in a single, more efficient pass.
  5. flatMap can be used for filter+map, one-to-many expansion, or element removal.
  6. Neither flat nor flatMap mutates the original array.

Explain-It Challenge

Explain without notes:

  1. What does [1, [2, [3]]].flat() return? What about .flat(Infinity)?
  2. How is flatMap different from just calling .map().flat()?
  3. Write a flatMap call that takes ["hello world", "foo bar"] and produces ["hello", "world", "foo", "bar"].

Navigation: ← 1.22.g — sort() · 1.22.i — Functional Thinking →