skip to content

map, filter, reduce and flatMap

The transformation trio turns loops into declarative pipelines, and reduce is the general-purpose one that can express the other two. Interviewers ask you to reimplement map or reduce, or to collapse a nested chain into a single pass, to test that you understand accumulators rather than memorized recipes.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

5

In JavaScript, what is the difference between Array.prototype.map and Array.prototype.filter, and what does each one return?

level: juniorimportance: must knowfreq 82%

answer

  1. one transforms, one selects
  2. same length versus shorter
  3. return value becomes element or test
  4. braced arrow body returns nothing

basics

~20 s

Array.prototype.map builds a new array of the same length by replacing each element with whatever its callback returns. Array.prototype.filter builds a new array holding only the elements whose callback returned a truthy value. Neither changes the original array.

solid answer

~40 s

Both take a callback invoked as `(element, index, array)` and both return a brand-new array, leaving the source alone. The difference is what the callback's return value means. For `map`, the returned value *becomes* the element, so the result always has the same length as the source — a transformation. For `filter`, the returned value is only tested for truthiness and is then thrown away; the original element is kept or dropped, so the result has the same elements but a length between zero and the source length — a selection. The classic beginner bug is a braced arrow body in `map`: `[1,2,3].map(n => { n * 2 })` has no `return`, so every callback yields `undefined` and you get `[undefined, undefined, undefined]`.

code

javascript · 10 lines
javascript
const users = [
  { name: 'ada', active: true },
  { name: 'linus', active: false },
  { name: 'grace', active: true },
];

console.log(users.map(u => u.name));            // ['ada', 'linus', 'grace']
console.log(users.filter(u => u.active).length); // 2
console.log([1, 2, 3].map(n => { n * 2 }));      // [undefined, undefined, undefined]
console.log(users.length);                       // 3 — source untouched

go deeper

for a junior

Be able to say in one breath that map transforms every element into a new array of the same length, and filter keeps a subset, and that both return a new array. Recognise the braced-arrow bug that yields an array of undefined.

for a middle

Explain the mechanics: the callback receives element, index and array; map writes the return value into the slot while filter only coerces it to a boolean and keeps the original element. Note that the resulting copy is shallow.

for a senior

Show production judgment: keep callbacks pure so a map does not quietly mutate shared objects, and justify chain order by what the predicate needs to see, not just by element count.

for a principal

Own the tradeoff between an expressive chain and a single pass over large data, and set the team convention for where declarative pipelines are the default and where intermediate-array allocation makes a hand-written pass worth its cost.

## The two methods side by side `Array.prototype.map` and `Array.prototype.filter` are the two most-used array transformation methods in JavaScript. Both are *higher-order*: you hand them a function (the callback) and they call it once per element. Both call the callback with three arguments — the element, its index, and the array being traversed — and both accept an optional second argument that becomes `this` inside a non-arrow callback. Both return a **new** array and never reassign elements of the source. The whole difference is in how each method interprets the callback's return value. ```js const nums = [1, 2, 3, 4]; nums.map(n => n * 2); // [2, 4, 6, 8] — same length, new values nums.filter(n => n > 2); // [3, 4] — original values, fewer of them nums; // [1, 2, 3, 4] — untouched ``` ## map: the return value becomes the element `map` treats the callback as a *transformer*. Whatever the callback returns is written into the corresponding slot of the result. Because there is exactly one output slot per input element, `result.length === source.length` always holds for `map` — there is no way for a `map` callback to drop an element. That one-to-one guarantee is why the missing-`return` bug is so common. An arrow function with an expression body returns the expression implicitly; an arrow function with a *braced* body does not. ```js [1, 2, 3].map(n => n * 2); // [2, 4, 6] [1, 2, 3].map(n => { n * 2 }); // [undefined, undefined, undefined] [1, 2, 3].map(n => ({ v: n })); // [{v:1},{v:2},{v:3}] — parens, or the braces read as a block ``` Returning an object literal from a concise arrow body needs the parentheses shown above, otherwise `{` starts a block statement rather than an object. ## filter: the return value is only a test `filter` treats the callback as a *predicate*. It coerces the returned value with the same truthiness rules the language uses everywhere (`0`, `-0`, `0n`, `""`, `null`, `undefined`, `NaN` and `false` are falsy; everything else is truthy) and then keeps or drops the **original** element. The callback's return value never appears in the output. ```js [0, 1, 2, 3].filter(n => n % 2); // [1, 3] — 1 is truthy, 0 is falsy ['a', '', 'b', null].filter(Boolean); // ['a', 'b'] — the idiomatic "drop falsy" trick ``` Because only truthiness matters, a predicate does not have to return a real boolean, though returning one is clearer. `filter(Boolean)` works precisely because `Boolean` used as a one-argument function is exactly the truthiness test. ## Neither method mutates the source array Both are copying methods: they allocate a new array and leave the receiver's contents alone. The copy is *shallow*, though — if the elements are objects, the new array holds the same object references, so mutating `result[0].name` is visible through the original array too. A `map` callback that mutates the object it is handed is a side effect the method itself cannot prevent, and it is a frequent source of confusing bugs; keep the callback pure and return a new value instead. ## Order matters when you chain them These methods return arrays, so they chain. Two rules of thumb: ```js users.filter(u => u.active).map(u => u.name); // fewer transformations users.map(u => u.name).filter(Boolean); // transform first, then test the result ``` Putting `filter` first means `map` runs on fewer elements. But the order is not merely a performance choice: the two orders test different things. In the first line the predicate sees the whole user object; in the second it sees only the produced name. Swap them blindly and the predicate may no longer have the property it needs. ## What interviewers listen for A confident answer names the return type of each method, states the length relationship (`map` preserves it, `filter` cannot grow it), says that both are non-mutating, and mentions the truthiness rule for the predicate. Mentioning the braced-arrow `undefined` trap unprompted signals you have actually debugged this in real code.

  • Why does an arrow callback with curly braces so often break a map call?
    A concise arrow body (`n => n * 2`) returns its expression implicitly; adding braces turns the body into a statement block, so the function returns `undefined` unless you write an explicit `return`. Since `map` writes the callback's return value into every slot, the result is an array of `undefined` of the right length — no error, just silently wrong data.
  • Does a filter predicate have to return an actual boolean?
    No. `filter` applies the language's truthiness coercion to whatever comes back, so `n => n % 2` works: `1` is truthy, `0` is falsy. That is also why `array.filter(Boolean)` is the idiomatic way to drop `null`, `undefined`, `''`, `0` and `NaN`. Returning a real boolean is still clearer for readers, and avoids surprises when the expression can produce an object.
  • In a chain, does it matter whether filter or map comes first?
    Yes, in two ways. Filtering first means the transformation runs on fewer elements. More importantly the predicate sees different data: before `map` it sees the source element, after `map` it sees the produced value. If the test needs a property that `map` discards, the filter must come first — the orders are not interchangeable refactors.

saying these in an interview costs you the question

  • Says map can skip elements by returning nothing
  • Claims map and filter mutate the original array
  • Thinks filter's callback return value ends up in the result
  • Believes a predicate must return true or false exactly
  • Assumes map's result may be shorter than the source

context

open as a page

What does the second argument to Array.prototype.reduce do, and what happens if you omit it?

level: middleimportance: must knowfreq 70%

basics

~20 s

The second argument is the initial accumulator value. Supply it and the callback runs once per element starting at index 0. Omit it and the first element becomes the accumulator, iteration starts at index 1, and an empty array throws a TypeError.

open as a page

What is the difference between Array.prototype.flat and Array.prototype.flatMap, and how deep does each one flatten?

level: middleimportance: should knowfreq 45%

basics

~10 s

Array.prototype.flat flattens nested arrays to a depth of 1 by default and accepts a depth argument, including Infinity for full flattening. Array.prototype.flatMap maps each element then flattens exactly one level, with no depth option.

open as a page

Why does ['1', '7', '11'].map(parseInt) return [1, NaN, 3] instead of [1, 7, 11]?

level: middleimportance: should knowfreq 55%

basics

~20 s

Array.prototype.map calls its callback with three arguments — element, index and array — so the index is passed as parseInt's radix parameter. The calls become parseInt('1', 0), parseInt('7', 1) and parseInt('11', 2), giving 1, NaN and 3.

open as a page

You need to group or count an array of objects by a key using reduce. How do you design the accumulator, and what is wrong with returning { ...acc, [key]: value } from the reducer?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Seed reduce with a fresh empty object or Map, then mutate that accumulator in place and return it. Spreading the accumulator on every iteration copies every key accumulated so far, turning a linear fold into quadratic work and heavy garbage for no safety benefit.

open as a page