Problem Solved

JavaScript · map filter reduce

Home › Courses › JavaScript › Problems › map vs filter vs reduce
🟨 Concept · 4 min read

map vs filter vs reduce.
Transform, select, aggregate.

Three array methods that cover 90% of array work. They look similar — same callback signature, same method chain — but they do different things. map transforms every element. filter selects some. reduce collapses the array to a single value. This page covers what each does, when to use each, and why the initial value in reduce matters more than people think.

Short answer: map transforms every element — same-length array. filter selects matching elements — same-or-shorter array. reduce aggregates to a single value — number, string, object, anything. Use map when you want to change each element. Use filter when you want to keep some elements. Use reduce when you want one value out of many. Always pass an initial value to reduce — it makes it safe for empty arrays. Chain them for readable data pipelines.

✅ The three rules

Each one does exactly one thing.

map — transform

Same length in, same length out. Each element replaced by the callback's return value. Use for "change every element."

filter — select

Same or fewer elements. Only elements where the callback returns true are kept. Use for "keep matching elements."

reduce — aggregate

Any input, one output. The callback runs with an accumulator and each element. Use for "collapse to a single value."

Chain them

users.filter(...).map(...).reduce(...) — each method returns a new array (or a value for reduce), so they compose.

✅ The three in action

Same array, three operations.

three-methods.js
const users = [
{ name: 'Alice', age: 30, active: true },
{ name: 'Bob', age: 25, active: false },
{ name: 'Charlie', age: 35, active: true }
];

// map — transform every element
const names = users.map(u => u.name);
console.log(names);
// ['Alice', 'Bob', 'Charlie']

// filter — keep matching elements
const activeUsers = users.filter(u => u.active);
console.log(activeUsers.map(u => u.name));
// ['Alice', 'Charlie']

// reduce — collapse to a single value
const totalAge = users.reduce((sum, u) => sum + u.age, 0);
console.log(totalAge);
// 90 (30 + 25 + 35)

// Chain them — pipeline
const activeAges = users
.filter(u => u.active)
.map(u => u.age)
.reduce((sum, age) => sum + age, 0);
console.log(activeAges);
// 65 (30 + 35)
Array sizes:

Original: 3 elements.

map → 3 elements (same length).
['Alice', 'Bob', 'Charlie']

filter → 2 elements (same or fewer).
[{name: 'Alice', ...}, {name: 'Charlie', ...}]

reduce → 1 value (number).
90

Chain → 1 value (number).
65

The sizes are the key:

map preserves length.
filter preserves or shrinks.
reduce collapses to one value.

If you're unsure which to use, ask:
"Do I want a same-length array?" → map
"Do I want fewer elements?" → filter
"Do I want one value?" → reduce

The chain:
filter first (removes elements you don't need)
then map (transforms what's left)
then reduce (aggregates)
This is the most efficient order — filter
removes elements before map/reduce process them.

✅ Why reduce needs an initial value

The bug that only shows up on empty arrays.

reduce-initial.js
// ❌ Without initial value
const nums = [1, 2, 3];
const sum1 = nums.reduce((acc, n) => acc + n);
console.log(sum1); // 6

// But:
const empty = [];
// const sum2 = empty.reduce((acc, n) => acc + n);
// TypeError: Reduce of empty array with no initial value

// ✅ With initial value
const sum3 = empty.reduce((acc, n) => acc + n, 0);
console.log(sum3); // 0

// The rule: always pass an initial value.
const users = [{ name: 'Alice' }, { name: 'Bob' }];
const nameMap = users.reduce((acc, u) => {
acc[u.name] = u;
return acc;
}, {});
console.log(nameMap);
// { Alice: {name: 'Alice'}, Bob: {name: 'Bob'} }
Without initial value:

nums.reduce((acc, n) => acc + n)
→ initial value = first element (1)
→ iterate from second element (2)
→ 1 + 2 + 3 = 6
→ works, but for the wrong reason

empty.reduce((acc, n) => acc + n)
→ no initial value, no first element
→ TypeError
→ the bug shows up only on empty arrays

With initial value:

nums.reduce((acc, n) => acc + n, 0)
→ initial value = 0
→ iterate all elements
→ 0 + 1 + 2 + 3 = 6
→ works, and works for empty arrays too

The initial value can be anything:

reduce(..., 0) → sum (number)
reduce(..., '') → concatenation (string)
reduce(..., {}) → object accumulator
reduce(..., []) → array accumulator
reduce(..., new Map()) → Map accumulator

The most common production bug:
sum = items.reduce((a, b) => a + b) → crashes on empty
sum = items.reduce((a, b) => a + b, 0) → safe

Always pass the initial value.
The 2 extra characters are worth it.

✅ When NOT to use them

The three cases where a loop is better.

when-not-to.js
// ❌ Not map — you don't need the result
users.map(user => {
console.log(user.name);
});
// Use forEach instead: users.forEach(u => console.log(u.name));

// ❌ Not filter — you want to break early
const firstMatch = users.filter(u => u.active)[0];
// Use find instead: users.find(u => u.active);
// Or a for loop if you need true early-exit

// ❌ Not reduce for simple iteration
users.reduce((_, user) => {
console.log(user.name);
}, null);
// Use forEach. Reduce is for aggregating, not iterating.

// ✅ The right methods
users.forEach(u => console.log(u.name)); // side effects
const first = users.find(u => u.active); // first match
const hasActive = users.some(u => u.active); // any match
const allActive = users.every(u => u.active); // all match
The other array methods:

forEach → iterate for side effects (no return value)
find → first matching element (or undefined)
findIndex → index of first matching element (or -1)
some → does at least one element match?
every → do all elements match?
includes → is this value in the array?

Use each for its purpose:

❌ users.map(u => { console.log(u) }) → wrong tool
✅ users.forEach(u => { console.log(u) })

❌ users.filter(u => u.active)[0] → inefficient
✅ users.find(u => u.active)

❌ users.filter(u => u.active).length > 0 → inefficient
✅ users.some(u => u.active)

❌ users.filter(u => !u.active).length === 0 → inefficient
✅ users.every(u => u.active)

The pattern: each method has one job.
Use it for that job. Don't use map for
iteration (that's forEach), don't use filter
for finding (that's find), don't use filter
• length for existence (that's some/every).

These are readability improvements, not
performance optimizations. But they make
the intent clearer — which is the point.

Don't use map for side effects. If you write users.map(u => console.log(u.name)) and ignore the return value, you're using map when you mean forEach. The difference matters because map creates a new array — for a side-effect loop, that array is wasted memory. More importantly, it misleads the reader: map says "I'm building a new array," and the reader looks for what you do with it. forEach says "I'm iterating for side effects." Use the right method for the right intent. This is a readability issue, not a performance one — but readability is what makes code maintainable.

⚠️ Common mistakes

The map/filter/reduce mistakes that cost real time.

1. map without using the result

You meant forEach. Fix: use forEach.

2. reduce without an initial value

Throws on empty arrays. Fix: always pass an initial value.

3. filter + [0] instead of find

Inefficient and unclear. Fix: use find.

4. filter(...).length > 0 instead of some

Inefficient. Fix: use some.

5. Chaining without thinking about order

Filter first (cheapest, removes elements), then map, then reduce. Fix: filter before transform.

6. reduce for simple iteration

Overcomplicated. Fix: use forEach or for...of.

7. Mutating the array inside map/filter

Confusing side effects. Fix: return new values, don't mutate.

8. Forgetting that reduce can return anything

Not just numbers — strings, objects, arrays. Fix: use the right accumulator for the job.

❓ Frequently asked

What is the difference between map, filter, and reduce?

Three operations on arrays. map transforms every element — same-length array. filter selects elements — same or smaller array. reduce aggregates — a single value. The rule: transform with map, select with filter, aggregate with reduce.

When should I use map instead of a for loop?

Use map when you want to transform each element of an array into a new array. It's more declarative than a for loop. Use a for loop when you need to break early, or when you're not building a new array. If you're using map and not using the returned array, you want forEach instead.

Why does reduce have an initial value?

The second argument to reduce is the initial value of the accumulator. If you omit it, reduce uses the first element of the array as the initial value and starts iterating from the second element. This works for some cases but breaks for others (summing an empty array). Always pass an initial value.

Can I chain map, filter, and reduce?

Yes, and this is one of the reasons they're powerful. Example: users.filter(u => u.active).map(u => u.name).reduce((acc, name) => acc + ', ' + name, ''). Each method returns a new array (or a value for reduce), so you can chain them. For most cases, the chained version is fine.

What does reduce return for an empty array?

If you pass an initial value, reduce returns that initial value for an empty array. If you don't pass one, reduce throws a TypeError. This is why the initial value matters — it makes reduce safe for empty arrays.

Is map faster than a for loop?

No, and it's slightly slower in micro-benchmarks. map creates a new function scope per call, allocates a new array, and has some overhead a plain for loop doesn't. But the difference is in nanoseconds. The real question is readability. map is declarative. For most code, map is clearer. Measure before optimizing.

🔗 Continue learning

🗺️ JavaScript learning path
The full roadmap from variables to async — every concept in order

Next in this series:

📊 Array methods: map, filter, reduce
The three that cover 90% of array work — with real examples
← All JavaScript problems
Problem solved · JavaScript