Imagine you have a magical backpack. You walk through a forest, picking up items along the way. Every time you find something, you can decide: "Do I keep this? Do I combine it with what I already have? Do I throw something away?"
By the time you reach the end of the forest, your backpack contains exactly what you wanted β a single result crafted from everything you encountered.
That's reduce(). It's not just a method β it's a superpower. Most developers use map and filter and never truly understand reduce. But once you do, you'll never look at arrays the same way again.
array.reduce((accumulator, currentValue, index, array) => {
// Do something
return newAccumulator;
}, initialValue);π accumulator: Your backpack β what you carry forward
π¦ currentValue: The item you just picked up
π index: Where you are in the forest
π² array: The whole forest (rarely needed)
π initialValue: What's in your backpack before you start
Before we learn what makes reduce special, let's see how it can replace every other array method. This isn't just a party trick β it shows you that reduce is the fundamental building block.
// Map: double each number
[1, 2, 3].map(x => x * 2);
// [2, 4, 6]
// Same with reduce
[1, 2, 3].reduce((acc, x) => {
acc.push(x * 2);
return acc;
}, []);
// [2, 4, 6]Start with [], push transformed values
// Filter: keep even numbers
[1, 2, 3, 4].filter(x => x % 2 === 0);
// [2, 4]
// Same with reduce
[1, 2, 3, 4].reduce((acc, x) => {
if (x % 2 === 0) acc.push(x);
return acc;
}, []);
// [2, 4]Start with [], push only if condition met
// Find: first number > 2
[1, 2, 3, 4].find(x => x > 2);
// 3
// Same with reduce
[1, 2, 3, 4].reduce((found, x) => {
if (found !== undefined) return found;
if (x > 2) return x;
}, undefined);
// 3Stop accumulating once found
// Some: any even?
[1, 3, 5, 7].some(x => x % 2 === 0);
// false
// With reduce
[1, 3, 5, 7].reduce((hasEven, x) => {
return hasEven || x % 2 === 0;
}, false);
// false// Every: all even?
[2, 4, 6, 8].every(x => x % 2 === 0);
// true
// With reduce
[2, 4, 6, 8].reduce((allEven, x) => {
return allEven && x % 2 === 0;
}, true);
// true// Flat: flatten one level
[[1,2],[3,4]].flat();
// [1,2,3,4]
// With reduce
[[1,2],[3,4]].reduce((acc, arr) => {
return acc.concat(arr);
}, []);
// [1,2,3,4]One of reduce's superpowers is building frequency maps. Need to know how many times each element appears? Reduce makes it trivial.
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const frequency = fruits.reduce((count, fruit) => {
count[fruit] = (count[fruit] || 0) + 1;
return count;
}, {});
console.log(frequency);
// { apple: 3, banana: 2, orange: 1 }
// The magic line:
// count[fruit] = (count[fruit] || 0) + 1;
// If exists, increment; if not, start at 0 then add 1const text = "hello world";
const charCount = text.split('').reduce((count, char) => {
count[char] = (count[char] || 0) + 1;
return count;
}, {});
console.log(charCount);
// { h:1, e:1, l:3, o:2, ' ':1, w:1, r:1, d:1 }
// Same pattern works for ANY groupingconst people = [
{ name: 'Alice', age: 30, city: 'NYC' },
{ name: 'Bob', age: 25, city: 'LA' },
{ name: 'Charlie', age: 30, city: 'NYC' },
{ name: 'Diana', age: 25, city: 'Chicago' }
];
const groupedByAge = people.reduce((groups, person) => {
const age = person.age;
if (!groups[age]) groups[age] = [];
groups[age].push(person);
return groups;
}, {});
console.log(groupedByAge);
// {
// 30: [{name:'Alice'}, {name:'Charlie'}],
// 25: [{name:'Bob'}, {name:'Diana'}]
// }const ages = [22, 25, 31, 28, 35, 42, 27, 33, 29, 41, 38];
const ageGroups = ages.reduce((groups, age) => {
const range = Math.floor(age / 10) * 10;
const key = `${range}-${range + 9}`;
groups[key] = (groups[key] || 0) + 1;
return groups;
}, {});
console.log(ageGroups);
// { '20-29': 5, '30-39': 4, '40-49': 2 }// Universal pattern for counting
array.reduce((acc, item) => {
acc[item] = (acc[item] || 0) + 1;
return acc;
}, {});
// Universal pattern for grouping
array.reduce((acc, item) => {
const key = getKey(item);
if (!acc[key]) acc[key] = [];
acc[key].push(item);
return acc;
}, {});const nested = [[1,2], [3,4], [5,6]];
const flattened = nested.reduce((flat, arr) => {
return flat.concat(arr);
}, []);
// [1,2,3,4,5,6]
// Deep flatten with recursion
const deepNested = [1, [2, [3, 4], 5], 6];
const deepFlatten = (arr) => arr.reduce((flat, item) => {
return flat.concat(
Array.isArray(item) ? deepFlatten(item) : item
);
}, []);
console.log(deepFlatten(deepNested));
// [1,2,3,4,5,6]const objects = [
{ a: 1, b: 2 },
{ b: 3, c: 4 },
{ d: 5 }
];
const merged = objects.reduce((result, obj) => {
return { ...result, ...obj };
}, {});
console.log(merged);
// { a: 1, b: 3, c: 4, d: 5 }
// Note: later objects overwrite earlier onesconst items = [
{ id: 1, quantity: 2 },
{ id: 2, quantity: 3 },
{ id: 1, quantity: 1 },
{ id: 3, quantity: 4 },
{ id: 2, quantity: 2 }
];
// Merge by id, summing quantities
const merged = items.reduce((acc, item) => {
const existing = acc.find(i => i.id === item.id);
if (existing) {
existing.quantity += item.quantity;
} else {
acc.push({ ...item });
}
return acc;
}, []);
console.log(merged);
// [{id:1, quantity:3}, {id:2, quantity:5}, {id:3, quantity:4}]const arrays = [
[1, 2, 3, 4],
[2, 3, 4, 5],
[3, 4, 5, 6]
];
const intersection = arrays.reduce((common, arr) => {
return common.filter(item => arr.includes(item));
});
console.log(intersection);
// [3, 4] (appears in all arrays)
// Union of arrays
const union = arrays.reduce((all, arr) => {
return [...new Set([...all, ...arr])];
}, []);
// [1,2,3,4,5,6]This is where reduce truly shines. Need to join data from different sources β like SQL joins? Reduce makes it elegant.
// Our data sources
const customers = [
{ id: 1, name: 'Alice', email: 'alice@email.com' },
{ id: 2, name: 'Bob', email: 'bob@email.com' },
{ id: 3, name: 'Charlie', email: 'charlie@email.com' }
];
const products = [
{ id: 101, name: 'Laptop', price: 999 },
{ id: 102, name: 'Mouse', price: 25 },
{ id: 103, name: 'Keyboard', price: 75 }
];
const orders = [
{ id: 1001, customerId: 1, productId: 101, quantity: 1 },
{ id: 1002, customerId: 2, productId: 102, quantity: 2 },
{ id: 1003, customerId: 1, productId: 103, quantity: 1 },
{ id: 1004, customerId: 3, productId: 101, quantity: 1 },
{ id: 1005, customerId: 2, productId: 101, quantity: 1 }
];
// First, create lookup maps (for O(1) access)
const customerMap = customers.reduce((map, customer) => {
map[customer.id] = customer;
return map;
}, {});
const productMap = products.reduce((map, product) => {
map[product.id] = product;
return map;
}, {});
// Now join orders with customers and products
const enrichedOrders = orders.reduce((result, order) => {
const customer = customerMap[order.customerId];
const product = productMap[order.productId];
result.push({
orderId: order.id,
customerName: customer?.name,
customerEmail: customer?.email,
productName: product?.name,
productPrice: product?.price,
quantity: order.quantity,
total: product?.price * order.quantity
});
return result;
}, []);
console.log(enrichedOrders);
// [
// { orderId:1001, customerName:'Alice', productName:'Laptop', total:999, ... },
// { orderId:1002, customerName:'Bob', productName:'Mouse', total:50, ... },
// ...etc
// ]// Already did LEFT JOIN above β all orders included
// even if customer/product missing (though we'd get undefined)const innerJoined = orders.reduce((result, order) => {
const customer = customerMap[order.customerId];
const product = productMap[order.productId];
if (customer && product) {
result.push({ /* enriched order */ });
}
return result;
}, []);// Total spent per customer
const customerSpending = orders.reduce((result, order) => {
const product = productMap[order.productId];
const total = product?.price * order.quantity || 0;
if (!result[order.customerId]) {
result[order.customerId] = {
customerId: order.customerId,
customerName: customerMap[order.customerId]?.name,
totalSpent: 0,
orders: []
};
}
result[order.customerId].totalSpent += total;
result[order.customerId].orders.push(order.id);
return result;
}, {});
console.log(Object.values(customerSpending));
// [
// { customerId:1, name:'Alice', totalSpent:1074, orders:[1001,1003] },
// { customerId:2, name:'Bob', totalSpent:1074, orders:[1002,1005] },
// { customerId:3, name:'Charlie', totalSpent:999, orders:[1004] }
// ]// Customers with their orders nested
const customersWithOrders = customers.reduce((result, customer) => {
const customerOrders = orders.filter(o => o.customerId === customer.id);
result.push({
...customer,
orders: customerOrders.map(order => ({
...order,
product: productMap[order.productId]
})),
totalSpent: customerOrders.reduce((sum, order) =>
sum + (productMap[order.productId]?.price * order.quantity), 0)
});
return result;
}, []);// Create a pipeline of functions
const pipe = (...fns) => (x) =>
fns.reduce((acc, fn) => fn(acc), x);
const add1 = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const transform = pipe(add1, double, square);
console.log(transform(3));
// add1: 4, double: 8, square: 64
// Compose (right to left)
const compose = (...fns) => (x) =>
fns.reduceRight((acc, fn) => fn(acc), x);const numbers = [10, 20, 30, 40, 50];
const runningAverages = numbers.reduce((acc, num, idx) => {
const sum = (acc.sum || 0) + num;
const avg = sum / (idx + 1);
acc.averages.push(avg);
acc.sum = sum;
return acc;
}, { averages: [], sum: 0 }).averages;
console.log(runningAverages);
// [10, 15, 20, 25, 30]const items = [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 1, name: 'Apple' },
{ id: 3, name: 'Orange' }
];
const uniqueById = items.reduce((unique, item) => {
if (!unique.some(u => u.id === item.id)) {
unique.push(item);
}
return unique;
}, []);
// Or using Map (more efficient)
const uniqueMap = items.reduce((map, item) => {
map.set(item.id, item);
return map;
}, new Map());
console.log([...uniqueMap.values()]);
// [{id:1,name:'Apple'}, {id:2,name:'Banana'}, {id:3,name:'Orange'}]const transactions = [
{ type: 'credit', amount: 100 },
{ type: 'debit', amount: 30 },
{ type: 'credit', amount: 50 },
{ type: 'debit', amount: 20 }
];
const balance = transactions.reduce((result, trans) => {
if (trans.type === 'credit') {
result.total += trans.amount;
result.credits.push(trans);
} else {
result.total -= trans.amount;
result.debits.push(trans);
}
return result;
}, { total: 0, credits: [], debits: [] });
console.log(balance);
// { total: 100, credits: [...], debits: [...] }const chunk = (arr, size) =>
arr.reduce((chunks, item, index) => {
const chunkIndex = Math.floor(index / size);
if (!chunks[chunkIndex]) chunks[chunkIndex] = [];
chunks[chunkIndex].push(item);
return chunks;
}, []);
console.log(chunk([1,2,3,4,5,6,7], 3));
// [[1,2,3], [4,5,6], [7]]const users = [
{ id: 'abc123', name: 'Alice' },
{ id: 'def456', name: 'Bob' },
{ id: 'ghi789', name: 'Charlie' }
];
const userMap = users.reduce((map, user) => {
map[user.id] = user;
return map;
}, {});
console.log(userMap.def456);
// { id: 'def456', name: 'Bob' }
// This is O(1) lookup vs O(n) with find()!// This loops through array 3 times!
const result = array
.filter(x => x > 10) // Loop 1
.map(x => x * 2) // Loop 2
.reduce((sum, x) => sum + x, 0); // Loop 3// One loop does everything!
const result = array.reduce((acc, x) => {
if (x > 10) {
acc += x * 2;
}
return acc;
}, 0);acc[key] = (acc[key]||0) + 1if(!acc[key]) acc[key]=[]; acc[key].push(val)acc[item.id] = item...acc, ...objif(cond) acc.push(val)Test your reduce mastery:
You've learned that reduce isn't just for summing numbers. It's the ultimate array transformation tool:
Count anything
Combine arrays/objects
SQL-like data joins
Transform each item
Keep what matches
Categorize data
"I don't always use array methods, but when I do, I use reduce. Stay transformative, my friends."
No need to read this again β you've already reduced your confusion to mastery! πͺβ¨