JAVASCRIPT / ARRAYS
flatMap and flattening nested results
Use flatMap to expand each element into zero, one, or many results, drop items by returning [], and know why it flattens only one level.
What you will learn
- Turn one-to-many work into a single flatMap call instead of map plus a manual flatten
- Return [] from the callback to drop an element; return [a, b] to emit several
- Remember flatMap unwraps exactly one level, and only when the return is an array
- Do parsing or validation once inside the callback instead of in filter and again in map
Understanding flatMap and flattening nested results
arr.flatMap(f) produces the same result as arr.map(f).flat(), but the interesting shift is in the callback's contract. With map the callback returns the single element that replaces the input; with flatMap it returns the batch of elements that input contributes. That is why the output length is the sum of the batch sizes rather than the input length, and why flatMap is the tool for splitting each string into words or listing every item across every order.
The flattening depth is fixed at one and cannot be changed, which follows directly from that contract: the callback hands back one batch, so flatMap unwraps exactly that one layer of batching. Anything still nested afterwards was nesting in your own data, so a callback that returns [[1, 2]] leaves [1, 2] sitting inside the result. Returns that are not arrays are appended untouched, so flatMap(x => x) over [1, [2, 3]] gives [1, 2, 3], and a returned string stays whole because a string is not an array.
Because an empty batch contributes nothing, flatMap also covers the filter-then-map pair in one pass, which matters when the test and the transformation share work: parse the value once inside the callback, return [parsed] when it worked and [] when it did not, instead of parsing in filter and again in map. Everything else matches map. The source array is not modified, the callback receives (element, index, array) where index counts positions in the source rather than the output, and holes in a sparse array are skipped instead of being passed to the callback.
const rows = ['1,2', '', '3,4,5'];
const numbers = rows.flatMap(row =>
row === '' ? [] : row.split(',').map(Number)
);
console.log(numbers);
console.log(numbers.length, rows.length);
const nested = [1, 2].flatMap(n => [[n, n]]);
console.log(nested);flatMap changes the callback's job from returning one replacement element to returning the batch of elements that input contributes, and splices each batch into one flat result.
Worked examples
Filter and map in one pass
Returning an empty array drops an element, so the parse and the keep-or-discard decision happen together.
const input = ['10', 'x', '20', '', '30'];
const numbers = input.flatMap(s => {
const n = Number(s);
return s.trim() !== '' && Number.isFinite(n) ? [n] : [];
});
console.log(numbers);
console.log(input.map(Number));Example explained
Line 1Number(s) runs once, and its result decides both whether the element survives and what value is kept.
Line 2'x' fails Number.isFinite and '' fails the trim test, so both return [] and vanish: five inputs, three outputs.
Line 3The map(Number) line shows what you get without flatMap: the same length as the input, with NaN and a misleading 0 still in it.
Expanding objects into a flat list
An inner map builds each element's batch and flatMap joins all the batches into one array.
const carts = [
{ user: 'ana', items: ['pen', 'ink'] },
{ user: 'bo', items: [] },
{ user: 'cy', items: ['pad'] }
];
const lines = carts.flatMap(cart =>
cart.items.map(item => `${cart.user}:${item}`)
);
console.log(lines);
console.log(carts.map(c => c.items));Example explained
Line 1cart.items.map(...) returns the batch for one cart, which is exactly what flatMap wants back.
Line 2bo has no items, so its batch is [] and that user contributes nothing to the result.
Line 3The second log is the map-only version: three nested arrays, still grouped by cart, needing a separate flatten.
One level only, and only for arrays
Shows that flatMap removes a single layer of nesting and leaves non-array returns exactly as they are.
console.log([1, [2, 3], 4].flatMap(x => x));
console.log(['hi', 'yo'].flatMap(w => w));
console.log(['hi', 'yo'].flatMap(w => [...w]));
console.log([[[1, 2]], [[3]]].flatMap(x => x));Example explained
Line 1Returning x unchanged spreads the nested [2, 3] and appends the plain numbers 1 and 4 as values.
Line 2A string is not an array, so 'hi' is appended whole; flatMap never splits strings into characters.
Line 3[...w] makes the batch a real array of characters, and that array is the thing flatMap splices in.
Line 4The last input is three levels deep and loses exactly one, so the result is still nested.
Important notes
The second argument of flatMap is thisArg, not a depth, so flatMap(f, 2) does not flatten two levels; it only sets this inside a non-arrow callback.
flatMap returns a new array and leaves the source untouched, but objects inside the batches are copied by reference, so mutating one in the result mutates it in the source too.
Common mistakes
Forgetting a return in one branch of a block-bodied callback: [1, 2, 3].flatMap(n => { if (n > 1) return [n]; }) gives [ undefined, 2, 3 ], because undefined is not an array and is appended as a value.
Using null, false, or undefined to signal 'drop this one' instead of []: none of them are arrays, so they land in the result and break every later join, reduce, or property access.
Expecting flatMap to fully flatten deep input: [[1, [2]]].flatMap(x => x) returns [ 1, [ 2 ] ], and arithmetic written for numbers then silently hits an array.
Try it yourself
Change, predict, then run
In a browser console, start from const tags = ['js, dom', '', 'css ,html'] and write a single flatMap call that produces ['js', 'dom', 'css', 'html'] with no empty strings and no stray spaces, then confirm the result length is 4.
Open the JavaScript workspaceCheck your understanding
What does [1, 2, 3].flatMap(n => n % 2 === 0 ? [n, n] : n) evaluate to?
- [1, 2, 2, 3]
- [1, [2, 2], 3]
- [[1], [2, 2], [3]]
- A TypeError, because a flatMap callback must always return an array
Show answer
flatMap splices array returns into the result one level deep and appends non-array returns unchanged, so 1 and 3 arrive as plain numbers while [2, 2] contributes two items. Option 2 is tempting because it is exactly what map alone would produce, but the built-in one-level flatten is the whole difference between the two methods; and a non-array return is allowed, so nothing throws.