Array.prototype.flatMap maps each element to a value — often an array — and then flattens the result by one level, in a single pass. It's the tool for "each input produces zero, one, or many outputs": splitting sentences into words, expanding a row into several, or dropping some inputs entirely.
Implement arrayFlatMap(arr, callback, thisArg). Call callback on each element; if it returns an array, its items are spliced into the result (flattened one level); if it returns a non-array, that value is pushed as-is. The result is a new array.
function arrayFlatMap(arr, callback, thisArg) {
// callback: (value, index, array) => mappedValueOrArray
// returns a new array, flattened one level.
}
arrayFlatMap([1, 2, 3], (x) => [x, x * 2]); // [1, 2, 2, 4, 3, 6]
arrayFlatMap([1, 2, 3], (x) => x * 10); // [10, 20, 30] — non-array pushed as-is
// Returning [] drops the element; returning many expands it:
arrayFlatMap([1, 2, 3, 4], (x) => (x % 2 === 0 ? [x] : [])); // [2, 4]
arrayFlatMap([1, 2], (x) => [[x]]); // [[1], [2]] — only ONE level flattened
[[x]] leaves the inner array nested. flatMap never flattens deeply.[] acts as a filter — returning an empty array contributes nothing, so flatMap can drop elements as well as expand them.map, holes in a sparse array are not visited.(value, index, array).You'll map each element to a value and, when that value is an array, spill its contents straight into the result — a map and a one-level flatten fused into one loop.
Sometimes one input should produce more than one output — a sentence becomes several words, an order becomes several line items — and sometimes it should produce none, dropping the input. Plain map can't do that: it returns exactly one output per input. flatMap lets your callback return an array, then flattens those arrays one level deep, so returning [a, b] yields two elements, returning [] yields none, and returning one value yields one. You're rebuilding it as arrayFlatMap(arr, callback, thisArg).
Run the callback on each element to get a mapped value. If that value is an array, its items get spliced into the result; if it's not, it's pushed as a single item. The flatten is exactly one level — you unwrap the array the callback returns, and no deeper.
The obvious version just maps:
function arrayFlatMapNaive(arr, callback) {
return arr.map(callback);
}
That gets you the mapping but skips the flatten. For [1, 2, 3] with x => [x, x * 2], map returns [[1, 2], [2, 4], [3, 6]] — an array of arrays — where flatMap should return [1, 2, 2, 4, 3, 6]. You could patch it to arr.map(callback).flat(), and that's actually correct, but it does two passes and builds a throwaway nested array in between. flatMap exists to do it in one pass, so we'll build the result directly.
function arrayFlatMap(arr, callback, thisArg) {
const result = [];
for (let i = 0; i < arr.length; i++) {
if (!(i in arr)) continue; // map skips holes, so flatMap does too
const mapped = callback.call(thisArg, arr[i], i, arr);
// Flatten by ONE level: if the callback returned an array, push its
// elements individually; otherwise push the value itself.
if (Array.isArray(mapped)) {
for (let j = 0; j < mapped.length; j++) result.push(mapped[j]);
} else {
result.push(mapped);
}
}
return result;
}
module.exports = { arrayFlatMap };
The single loop does the map and the flatten together. For each element we compute mapped, then branch: an array gets its items pushed one by one (that inner for is the one-level flatten — we copy the array's elements, but never recurse into their contents), and a non-array is pushed whole. Because we only ever unwrap the array the callback returned, deeper nesting like [[x]] survives. And when the callback returns [], the inner loop runs zero times, so nothing is added — that's how flatMap doubles as a filter. The i in arr guard makes it skip holes, matching map.
Take arrayFlatMap([1, 2, 3], (x) => (x === 2 ? [] : [x, x])):
callback(1, 0, arr) → [1, 1]. It's an array, so push 1, then 1. result = [1, 1].callback(2, ...) → []. It's an array of length 0, so the inner loop adds nothing. result = [1, 1].callback(3, ...) → [3, 3]. Push 3, then 3. result = [1, 1, 3, 3].Result: [1, 1, 3, 3] — element 2 was dropped by returning [], and 1 and 3 were each expanded to two.
map leaves [[1,2],[2,4]]. flatMap must unwrap one level so the result is [1,2,2,4].[[x]] should stay [[x]], not collapse to [x]. Push the callback's array items directly; don't recurse.[] is meaningful — it's not an error or a no-op you skip; it deliberately contributes zero elements, which is the filter behavior.flatMap(depth) doesn't exist — flatMap is fixed at depth 1. For deeper flattening, map(...).flat(depth) is the explicit route.bind — flatMap is the array form of the "bind" operation (>>=) from functional programming: map a value into a container, then join one level. Recognizing this pattern helps when you meet Promise-chaining or optional/Maybe types.flatMap yields results lazily instead of building the whole array, avoiding the intermediate allocation entirely.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
Array.prototype.flatMap maps each element to a value — often an array — and then flattens the result by one level, in a single pass. It's the tool for "each input produces zero, one, or many outputs": splitting sentences into words, expanding a row into several, or dropping some inputs entirely.
Implement arrayFlatMap(arr, callback, thisArg). Call callback on each element; if it returns an array, its items are spliced into the result (flattened one level); if it returns a non-array, that value is pushed as-is. The result is a new array.
function arrayFlatMap(arr, callback, thisArg) {
// callback: (value, index, array) => mappedValueOrArray
// returns a new array, flattened one level.
}
arrayFlatMap([1, 2, 3], (x) => [x, x * 2]); // [1, 2, 2, 4, 3, 6]
arrayFlatMap([1, 2, 3], (x) => x * 10); // [10, 20, 30] — non-array pushed as-is
// Returning [] drops the element; returning many expands it:
arrayFlatMap([1, 2, 3, 4], (x) => (x % 2 === 0 ? [x] : [])); // [2, 4]
arrayFlatMap([1, 2], (x) => [[x]]); // [[1], [2]] — only ONE level flattened
[[x]] leaves the inner array nested. flatMap never flattens deeply.[] acts as a filter — returning an empty array contributes nothing, so flatMap can drop elements as well as expand them.map, holes in a sparse array are not visited.(value, index, array).You'll map each element to a value and, when that value is an array, spill its contents straight into the result — a map and a one-level flatten fused into one loop.
Sometimes one input should produce more than one output — a sentence becomes several words, an order becomes several line items — and sometimes it should produce none, dropping the input. Plain map can't do that: it returns exactly one output per input. flatMap lets your callback return an array, then flattens those arrays one level deep, so returning [a, b] yields two elements, returning [] yields none, and returning one value yields one. You're rebuilding it as arrayFlatMap(arr, callback, thisArg).
Run the callback on each element to get a mapped value. If that value is an array, its items get spliced into the result; if it's not, it's pushed as a single item. The flatten is exactly one level — you unwrap the array the callback returns, and no deeper.
The obvious version just maps:
function arrayFlatMapNaive(arr, callback) {
return arr.map(callback);
}
That gets you the mapping but skips the flatten. For [1, 2, 3] with x => [x, x * 2], map returns [[1, 2], [2, 4], [3, 6]] — an array of arrays — where flatMap should return [1, 2, 2, 4, 3, 6]. You could patch it to arr.map(callback).flat(), and that's actually correct, but it does two passes and builds a throwaway nested array in between. flatMap exists to do it in one pass, so we'll build the result directly.
function arrayFlatMap(arr, callback, thisArg) {
const result = [];
for (let i = 0; i < arr.length; i++) {
if (!(i in arr)) continue; // map skips holes, so flatMap does too
const mapped = callback.call(thisArg, arr[i], i, arr);
// Flatten by ONE level: if the callback returned an array, push its
// elements individually; otherwise push the value itself.
if (Array.isArray(mapped)) {
for (let j = 0; j < mapped.length; j++) result.push(mapped[j]);
} else {
result.push(mapped);
}
}
return result;
}
module.exports = { arrayFlatMap };
The single loop does the map and the flatten together. For each element we compute mapped, then branch: an array gets its items pushed one by one (that inner for is the one-level flatten — we copy the array's elements, but never recurse into their contents), and a non-array is pushed whole. Because we only ever unwrap the array the callback returned, deeper nesting like [[x]] survives. And when the callback returns [], the inner loop runs zero times, so nothing is added — that's how flatMap doubles as a filter. The i in arr guard makes it skip holes, matching map.
Take arrayFlatMap([1, 2, 3], (x) => (x === 2 ? [] : [x, x])):
callback(1, 0, arr) → [1, 1]. It's an array, so push 1, then 1. result = [1, 1].callback(2, ...) → []. It's an array of length 0, so the inner loop adds nothing. result = [1, 1].callback(3, ...) → [3, 3]. Push 3, then 3. result = [1, 1, 3, 3].Result: [1, 1, 3, 3] — element 2 was dropped by returning [], and 1 and 3 were each expanded to two.
map leaves [[1,2],[2,4]]. flatMap must unwrap one level so the result is [1,2,2,4].[[x]] should stay [[x]], not collapse to [x]. Push the callback's array items directly; don't recurse.[] is meaningful — it's not an error or a no-op you skip; it deliberately contributes zero elements, which is the filter behavior.flatMap(depth) doesn't exist — flatMap is fixed at depth 1. For deeper flattening, map(...).flat(depth) is the explicit route.bind — flatMap is the array form of the "bind" operation (>>=) from functional programming: map a value into a container, then join one level. Recognizing this pattern helps when you meet Promise-chaining or optional/Maybe types.flatMap yields results lazily instead of building the whole array, avoiding the intermediate allocation entirely.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.