Episode 1 — Fundamentals / 1.22 — Array Methods
1.22.c — reduce()
In one sentence:
reduce()processes every element left-to-right, feeding each result into the next iteration, and accumulates the entire array into a single output value of any type.
Navigation: ← 1.22.b — filter() · 1.22.d — forEach() →
1. What reduce() does
While map returns an array and filter returns a subset, reduce can return anything — a number, string, object, array, or even a boolean. It processes elements one at a time, carrying an accumulator from iteration to iteration.
const nums = [1, 2, 3, 4, 5];
const sum = nums.reduce((accumulator, current) => accumulator + current, 0);
console.log(sum); // 15
2. Syntax
const result = arr.reduce(callback(accumulator, currentValue, index, array), initialValue);
| Parameter | Description |
|---|---|
accumulator | The running total / accumulated value from previous iterations |
currentValue | The current element being processed |
index | (optional) The index of the current element |
array | (optional) The original array |
initialValue | The starting value for the accumulator (first call) |
| Return | The final accumulated value after all iterations |
3. ALWAYS provide an initial value
// WITH initial value (recommended)
[1, 2, 3].reduce((acc, cur) => acc + cur, 0); // 6
// WITHOUT initial value (dangerous)
[1, 2, 3].reduce((acc, cur) => acc + cur); // 6 — works here, but...
// Problem 1: empty array throws
[].reduce((acc, cur) => acc + cur);
// TypeError: Reduce of empty array with no initial value
// Problem 2: single element returns it without calling callback
[5].reduce((acc, cur) => acc + cur);
// 5 — callback was never called!
// WITH initial value: safe for all cases
[].reduce((acc, cur) => acc + cur, 0); // 0 — no error
[5].reduce((acc, cur) => acc + cur, 0); // 5 — callback called once
Rule: Always provide the second argument to reduce. It prevents empty-array crashes and makes intent clear.
4. Step-by-step walkthrough
Let us trace [10, 20, 30, 40].reduce((acc, cur) => acc + cur, 0):
| Iteration | accumulator | currentValue | Return (acc + cur) |
|---|---|---|---|
| 1 | 0 (initial) | 10 | 10 |
| 2 | 10 | 20 | 30 |
| 3 | 30 | 30 | 60 |
| 4 | 60 | 40 | 100 |
Final result: 100
Without initial value
[10, 20, 30, 40].reduce((acc, cur) => acc + cur) — no second argument:
| Iteration | accumulator | currentValue | Return |
|---|---|---|---|
| 1 | 10 (first element) | 20 (starts at index 1) | 30 |
| 2 | 30 | 30 | 60 |
| 3 | 60 | 40 | 100 |
Same result here, but only 3 iterations instead of 4. The first element becomes the initial accumulator — risky when the array might be empty.
5. Reducing to a number
Sum
const prices = [29.99, 15.50, 42.00, 8.99];
const total = prices.reduce((sum, price) => sum + price, 0);
console.log(total); // 96.48
Product
const factors = [2, 3, 4, 5];
const product = factors.reduce((prod, n) => prod * n, 1);
console.log(product); // 120
Max value
const temps = [22, 35, 18, 42, 29];
const max = temps.reduce((highest, t) => t > highest ? t : highest, -Infinity);
console.log(max); // 42
// Or simply: Math.max(...temps) — but reduce works for huge arrays
Average
const scores = [85, 92, 78, 96, 88];
const avg = scores.reduce((sum, score, index, arr) => {
sum += score;
if (index === arr.length - 1) return sum / arr.length;
return sum;
}, 0);
console.log(avg); // 87.8
A cleaner approach:
const avg2 = scores.reduce((sum, s) => sum + s, 0) / scores.length;
6. Reducing to an object
Frequency counter
const fruits = ["apple", "banana", "apple", "cherry", "banana", "apple"];
const count = fruits.reduce((freq, fruit) => {
freq[fruit] = (freq[fruit] || 0) + 1;
return freq;
}, {});
console.log(count);
// { apple: 3, banana: 2, cherry: 1 }
Grouping by category
const products = [
{ name: "Laptop", category: "electronics", price: 999 },
{ name: "Shirt", category: "clothing", price: 29 },
{ name: "Phone", category: "electronics", price: 699 },
{ name: "Pants", category: "clothing", price: 49 },
{ name: "Book", category: "books", price: 15 },
];
const grouped = products.reduce((groups, product) => {
const key = product.category;
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(product);
return groups;
}, {});
console.log(grouped);
// {
// electronics: [{ name: "Laptop", ... }, { name: "Phone", ... }],
// clothing: [{ name: "Shirt", ... }, { name: "Pants", ... }],
// books: [{ name: "Book", ... }],
// }
Index by ID (lookup table)
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Carol" },
];
const byId = users.reduce((map, user) => {
map[user.id] = user;
return map;
}, {});
console.log(byId[2]); // { id: 2, name: "Bob" }
// O(1) lookup instead of O(n) with find()
7. Reducing to an array
Flatten nested arrays
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flat); // [1, 2, 3, 4, 5, 6]
// Modern alternative: nested.flat()
Unique values
const nums = [1, 3, 5, 3, 1, 5, 7, 7, 9];
const unique = nums.reduce((acc, n) => {
if (!acc.includes(n)) acc.push(n);
return acc;
}, []);
console.log(unique); // [1, 3, 5, 7, 9]
// Modern alternative: [...new Set(nums)]
Expand / duplicate elements
const items = ["a", "b", "c"];
const doubled = items.reduce((acc, item) => {
acc.push(item, item);
return acc;
}, []);
console.log(doubled); // ["a", "a", "b", "b", "c", "c"]
8. reduce is the "universal" array method
Every other array method can be built with reduce:
map from reduce
function myMap(arr, fn) {
return arr.reduce((acc, el, i) => {
acc.push(fn(el, i));
return acc;
}, []);
}
myMap([1, 2, 3], n => n * 2); // [2, 4, 6]
filter from reduce
function myFilter(arr, fn) {
return arr.reduce((acc, el, i) => {
if (fn(el, i)) acc.push(el);
return acc;
}, []);
}
myFilter([1, 2, 3, 4, 5], n => n % 2 === 0); // [2, 4]
some from reduce
function mySome(arr, fn) {
return arr.reduce((found, el) => found || fn(el), false);
}
This is conceptually important: reduce is the most powerful array method. But in practice, use the specific method (map, filter, some) for clarity.
9. reduceRight() — right to left
reduceRight works identically but iterates from the last element to the first.
const nums = [1, 2, 3, 4];
// Left to right: ((((0 - 1) - 2) - 3) - 4) = -10
nums.reduce((acc, n) => acc - n, 0); // -10
// Right to left: ((((0 - 4) - 3) - 2) - 1) = -10
nums.reduceRight((acc, n) => acc - n, 0); // -10 (same here, but different order)
Where reduceRight matters: function composition
const compose = (...fns) =>
x => fns.reduceRight((val, fn) => fn(val), x);
const add1 = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const transform = compose(square, double, add1);
// Reads right to left: add1 → double → square
// add1(3) = 4, double(4) = 8, square(8) = 64
console.log(transform(3)); // 64
10. Common mistakes
Forgetting initial value
// Crash on empty array
[].reduce((a, b) => a + b);
// TypeError: Reduce of empty array with no initial value
// Fix
[].reduce((a, b) => a + b, 0); // 0
Not returning the accumulator
// BUG: no return in if-else branch
const count = ["a", "b", "a"].reduce((acc, val) => {
if (val === "a") {
acc.a = (acc.a || 0) + 1;
}
// forgot return for non-"a" values!
}, {});
// acc becomes undefined after first "b"
// FIX: always return acc
const count2 = ["a", "b", "a"].reduce((acc, val) => {
if (val === "a") {
acc.a = (acc.a || 0) + 1;
}
return acc; // always return!
}, {});
Wrong initial value type
// Trying to build an object but starting with a number
const items = [{ price: 10 }, { price: 20 }];
// BUG
items.reduce((acc, item) => acc + item.price, {}); // "[object Object]20"
// FIX
items.reduce((acc, item) => acc + item.price, 0); // 30
11. Real-world examples
Calculate total price of cart
const cart = [
{ name: "Shirt", price: 25, qty: 2 },
{ name: "Pants", price: 50, qty: 1 },
{ name: "Socks", price: 5, qty: 3 },
];
const total = cart.reduce((sum, item) => sum + (item.price * item.qty), 0);
console.log(total); // 115
Build a summary object
const transactions = [
{ type: "income", amount: 5000 },
{ type: "expense", amount: 1200 },
{ type: "income", amount: 3000 },
{ type: "expense", amount: 800 },
{ type: "expense", amount: 500 },
];
const summary = transactions.reduce((acc, t) => {
acc[t.type] += t.amount;
acc.net = acc.income - acc.expense;
return acc;
}, { income: 0, expense: 0, net: 0 });
console.log(summary);
// { income: 8000, expense: 2500, net: 5500 }
Flatten and count tags
const posts = [
{ title: "Post 1", tags: ["js", "web"] },
{ title: "Post 2", tags: ["js", "react"] },
{ title: "Post 3", tags: ["css", "web", "react"] },
];
const tagCount = posts.reduce((acc, post) => {
post.tags.forEach(tag => {
acc[tag] = (acc[tag] || 0) + 1;
});
return acc;
}, {});
console.log(tagCount);
// { js: 2, web: 2, react: 2, css: 1 }
Pipeline: reduce as a runner
const pipeline = [
n => n * 2,
n => n + 10,
n => n / 3,
];
const result = pipeline.reduce((val, fn) => fn(val), 5);
// Step 1: 5 * 2 = 10
// Step 2: 10 + 10 = 20
// Step 3: 20 / 3 = 6.666...
console.log(result); // 6.666...
12. When NOT to use reduce
reduce is powerful but can hurt readability. Prefer specific methods when they exist:
// Over-engineered: using reduce for what map does
const doubled = nums.reduce((acc, n) => { acc.push(n * 2); return acc; }, []);
// Better: just use map
const doubled2 = nums.map(n => n * 2);
// Over-engineered: using reduce for what filter does
const evens = nums.reduce((acc, n) => { if (n % 2 === 0) acc.push(n); return acc; }, []);
// Better: just use filter
const evens2 = nums.filter(n => n % 2 === 0);
Use reduce when the output shape differs from the input (array to object, array to number, etc.) or when you need to combine multiple operations in a single pass.
Key takeaways
reduce()accumulates all elements into a single value of any type.- Always provide an
initialValue— prevents crashes on empty arrays and clarifies intent. - Always return the accumulator from the callback.
- Use
reducefor sums, frequency counters, grouping, flattening, and object building. reduceis the "universal" method —mapandfiltercan be built from it — but prefer the specific method when it exists.reduceRightiterates right-to-left; useful for function composition.
Explain-It Challenge
Explain without notes:
- Walk through
[3, 7, 2].reduce((acc, n) => acc + n, 10)step by step. What is the final result? - What happens if you call
reduceon an empty array without an initial value? - Write a
reducethat turns["red", "blue", "red", "green", "blue", "blue"]into{ red: 2, blue: 3, green: 1 }.
Navigation: ← 1.22.b — filter() · 1.22.d — forEach() →