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);
ParameterDescription
accumulatorThe running total / accumulated value from previous iterations
currentValueThe current element being processed
index(optional) The index of the current element
array(optional) The original array
initialValueThe starting value for the accumulator (first call)
ReturnThe 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):

IterationaccumulatorcurrentValueReturn (acc + cur)
10 (initial)1010
2102030
3303060
46040100

Final result: 100

Without initial value

[10, 20, 30, 40].reduce((acc, cur) => acc + cur) — no second argument:

IterationaccumulatorcurrentValueReturn
110 (first element)20 (starts at index 1)30
2303060
36040100

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

  1. reduce() accumulates all elements into a single value of any type.
  2. Always provide an initialValue — prevents crashes on empty arrays and clarifies intent.
  3. Always return the accumulator from the callback.
  4. Use reduce for sums, frequency counters, grouping, flattening, and object building.
  5. reduce is the "universal" method — map and filter can be built from it — but prefer the specific method when it exists.
  6. reduceRight iterates right-to-left; useful for function composition.

Explain-It Challenge

Explain without notes:

  1. Walk through [3, 7, 2].reduce((acc, n) => acc + n, 10) step by step. What is the final result?
  2. What happens if you call reduce on an empty array without an initial value?
  3. Write a reduce that turns ["red", "blue", "red", "green", "blue", "blue"] into { red: 2, blue: 3, green: 1 }.

Navigation: ← 1.22.b — filter() · 1.22.d — forEach() →