jsguides

Array.prototype.flatMap()

flatMap(callbackFn, thisArg)

flatMap() combines map() and flat() in a single method. It applies a callback to each element and then flattens the result by exactly one level. This is more efficient than chaining .map().flat() because it only iterates the array once rather than twice, avoiding the intermediate array that a two-step approach allocates.

The flattening depth is always 1. If the callback returns arrays nested more than one level deep, the outer level is flattened but inner nesting remains intact. Use .flat(n) after .map() if you need deeper flattening.

Syntax

flatMap(callbackFn, thisArg)

Parameters

ParameterTypeDefaultDescription
callbackFnfunction-Function that produces an element or array of elements for the new array
thisArganyundefinedValue to use as this when executing callbackFn

The callback receives (currentValue, index, array).

Return value

A new array formed by applying the callback and then flattening the results by one level. The original array is not modified.

Examples

Basic usage

const words = ["hello", "world"];
const chars = words.flatMap(word => word.split(''));
console.log(chars);
// ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']

flatMap runs word.split('') on each string, producing an array of character arrays, then flattens them by one level into a single array of individual characters. This avoids the intermediate [['h','e','l','l','o'], ['w','o','r','l','d']] that map alone would create, saving both memory and an extra loop iteration.

Flattening nested arrays

const nested = [[1, 2], [3, 4], [5]];
const flattened = nested.flatMap(arr => arr.map(x => x * 2));
console.log(flattened);
// [2, 4, 6, 8, 10]

When the source is already nested, flatMap flattens one level after applying the mapping function. Here each sub-array doubles its elements first, then flatMap merges all results into a single flat list rather than a list of nested arrays.

Removing empty slots

const arr = [1, , 3].flatMap(x => [x, x]);
console.log(arr);
// [1, 1, 3, 3]

flatMap skips empty slots in sparse arrays, which makes it a handy tool for cleaning up arrays with gaps. Regular map visits those slots and returns undefined, but flatMap treats them as if they were filtered out.

Common patterns

Flattening and transforming in one pass

Instead of two iterations:

const result = arr.map(x => [x, x * 2]).flat();

Chaining .map().flat() creates an intermediate array that flatMap avoids. For large datasets, this single-pass approach reduces memory pressure and the number of iteration steps the engine performs. The difference is especially visible in hot code paths like render loops or stream processing.

Use one iteration:

const result = arr.flatMap(x => [x, x * 2]);

Both approaches produce the same output, but flatMap does it with one allocation instead of two. The performance gain matters most when working with arrays of thousands of elements, where avoiding an intermediate copy is noticeable during scrolling, rendering, or data processing loops. For smaller arrays, the readability win alone makes flatMap the better default.

Filtering and mapping simultaneously

Return an empty array [] to exclude an element, or a one-element array [value] to include it. This replaces a .filter().map() chain with a single pass:

const items = [1, 2, 3, 4, 5];
const evenDoubled = items.flatMap(x => x % 2 === 0 ? [x * 2] : []);
console.log(evenDoubled);
// [4, 8]

Returning [] drops the element entirely, while [x * 2] keeps it transformed. This filter-and-map combo is a single traversal, which is cleaner than chaining .filter().map() and avoids constructing the filtered intermediate array. Use this technique whenever you need to both remove items and reshape the ones that remain in one loop.

Expanding records into individual items

const orders = [
  { id: 1, items: ['apple', 'banana'] },
  { id: 2, items: ['cherry'] }
];

const allItems = orders.flatMap(order => order.items);
console.log(allItems);
// ['apple', 'banana', 'cherry']

This pattern shines when working with relational or grouped data — orders containing line items, pages containing sections, or groups containing members. The callback simply returns the nested array, and flatMap handles the rest, producing a flat result you can iterate over directly without nested loops.

Conditional flattening

const items = [1, 2, 3, 4, 5];
const result = items.flatMap(x => x % 2 === 0 ? [x] : []);
console.log(result);
// [2, 4]

Depth is always 1

flatMap always flattens exactly one level. If your callback returns nested arrays more than one level deep, the inner nesting survives. This is by design — it keeps the method predictable. The example below wraps each element in a double-nested array to show what happens at the boundary.

const arr = [1, 2, 3];
const result = arr.flatMap(x => [[x, x * 2]]);
console.log(result);
// [[1, 2], [2, 4], [3, 6]] - outer array flattened, inner arrays remain

The callback returns [[x, x * 2]], which is an array containing one inner array. flatMap flattens the outer wrapper, leaving each inner [x, x * 2] array intact. If you need those inner arrays flattened too, chain another .flat() call.

To flatten deeper, chain .flat(n) after .map():

const deep = arr.map(x => [[x, x * 2]]).flat(2);

The key difference: flatMap always flattens one level. When the callback returns arrays nested two or more levels deep, you need the explicit .flat(n) chain. For the common case of single-level results, flatMap is the cleaner and faster choice.

flatMap() vs map().flat()

Both produce the same result for depth 1, but flatMap is preferred for performance and clarity:

const arr = [1, 2, 3];
arr.flatMap(x => [x, -x]);        // one pass
arr.map(x => [x, -x]).flat();     // two passes, two array allocations

Both produce [1, -1, 2, -2, 3, -3]. Use .map().flat(n) only when you need to flatten more than one level deep.

thisArg

The optional thisArg parameter sets the value of this inside the callback. This is most useful when the callback is a regular function (not an arrow function) that needs to reference an external context. Arrow functions capture this from the enclosing scope and ignore thisArg.

See Also