Array map, filter, reduce
map, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- javascript
- arrays
- map
- filter
- reduce
Frontend code is mostly “take a list, produce another list or a single value.” map, filter, and reduce name those three jobs. Interviewers care less about the API and more about whether you mutate, whether you chain wastefully, and whether you can implement reduce from scratch.
Three jobs
const nums = [1, 2, 3, 4, 5];
nums.map((n) => n * 2); // [2, 4, 6, 8, 10] — same length, project
nums.filter((n) => n % 2 === 0); // [2, 4] — subset
nums.reduce((sum, n) => sum + n, 0); // 15 — aggregate
- map — one output element per input (1:1 shape).
- filter — keep elements where the predicate is truthy.
- reduce — fold the list into any single value (number, object, Map, array…).
None of them mutate the original array (unless you mutate objects inside). The array reference is new for map/filter; reduce returns whatever you build.
map and filter in UI code
const visible = todos
.filter((t) => (showDone ? true : !t.done))
.map((t) => ({ ...t, label: t.title.toUpperCase() }));
Prefer pure callbacks. Side effects (DOM writes, network) inside map make order and re-runs hard to reason about — put those in forEach or a loop if you must, or better, outside.
reduce: start with an initial value
// group by key
function groupBy(arr, keyFn) {
return arr.reduce((acc, item) => {
const k = keyFn(item);
(acc[k] ??= []).push(item);
return acc;
}, {});
}
groupBy(
[
{ type: 'fruit', name: 'apple' },
{ type: 'veg', name: 'carrot' },
{ type: 'fruit', name: 'pear' },
],
(x) => x.type,
);
// { fruit: [...], veg: [...] }
Without an initial value, reduce uses the first element as the accumulator and starts at index 1 — easy to break on empty arrays:
[].reduce((a, b) => a + b); // TypeError
[].reduce((a, b) => a + b, 0); // 0
Always pass an initial value unless you deliberately want “first element” semantics.
Chaining cost
// two intermediate arrays
arr.filter(pred).map(fn);
// one pass
arr.reduce((acc, x) => {
if (pred(x)) acc.push(fn(x));
return acc;
}, []);
For small UI lists, chain for readability. For hot paths over huge arrays, one loop (or reduce) wins. Don’t optimize 20-item todo lists.
map is not for side effects
// bad smell
ids.map((id) => fetch(`/api/${id}`)); // returns promises you ignored
// intentional
await Promise.all(ids.map((id) => fetch(`/api/${id}`).then((r) => r.json())));
Implementing the mental model
function map(arr, fn) {
const out = [];
for (let i = 0; i < arr.length; i++) out.push(fn(arr[i], i, arr));
return out;
}
function filter(arr, fn) {
const out = [];
for (let i = 0; i < arr.length; i++) {
if (fn(arr[i], i, arr)) out.push(arr[i]);
}
return out;
}
function reduce(arr, fn, init) {
let acc = init;
let i = 0;
if (arguments.length < 3) {
if (arr.length === 0) throw new TypeError('Reduce of empty array');
acc = arr[0];
i = 1;
}
for (; i < arr.length; i++) acc = fn(acc, arr[i], i, arr);
return acc;
}
If you can write that on a whiteboard, the built-ins stop being magic.
Footguns
| Issue | Detail |
|---|---|
| Sparse arrays | Callbacks skip holes; length still includes them in some senses |
| Mutating objects inside map | New array, same nested refs — shallow |
filter(Boolean) |
Drops 0, '', false — may be intended |
| reduce object spreads in a loop | acc = { ...acc, [k]: v } is O(n²) for large n |
Interview answer
“map projects 1:1, filter selects, reduce folds to any value. I always give reduce an initial value. I chain filter/map for clarity on small data and collapse to one pass when profiling says so. I don’t use map for pure side effects.”
Related
Further reading
Related guides
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Destructuring Objects and ArraysObject and array destructuring — renames, defaults, nested patterns, rest, and parameter destructuring in real APIs.
- Functional Array PatternsPractical functional array techniques — flatMap, partitioning, indexing, zip, and when chaining hurts performance.
- Map and SetMap for any-key dictionaries and Set for unique values — iteration order, object keys, Weak variants, and when objects/arrays still win.
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.