What is Reduce?
The reduce() method is like a magical backpack that walks through an array, carrying something with it, and at each step, it can decide what to put in the backpack based on the current item.
In technical terms, reduce() executes a reducer function on each element of the array, resulting in a single output value. It's the most powerful array method because it can replicate map, filter, find, and more β all in one pass!
// The Anatomy of Reduce
array.reduce((accumulator, currentValue, index, array) => {
// Do something
return newAccumulator;
}, initialValue);
// accumulator: The backpack β what you carry forward
// currentValue: The current item you're looking at
// index: Where you are in the array
// array: The original array (rarely needed)
// initialValue: What's in your backpack before you start
// Simple example: Sum of numbers
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum); // 15Why Reduce is a Superpower
Most developers use map, filter, and find without realizing thatreduce can do everything they can β and more! Here's how:
// Map with Reduce
const numbers = [1, 2, 3, 4];
const doubled = numbers.reduce((acc, curr) => {
acc.push(curr * 2);
return acc;
}, []);
console.log(doubled); // [2, 4, 6, 8]
// Filter with Reduce
const evens = numbers.reduce((acc, curr) => {
if (curr % 2 === 0) acc.push(curr);
return acc;
}, []);
console.log(evens); // [2, 4]
// Find with Reduce
const firstEven = numbers.reduce((acc, curr) => {
if (acc !== undefined) return acc;
if (curr % 2 === 0) return curr;
}, undefined);
console.log(firstEven); // 2
// Every with Reduce
const allEven = numbers.reduce((acc, curr) => acc && curr % 2 === 0, true);
console.log(allEven); // false
// Some with Reduce
const hasEven = numbers.reduce((acc, curr) => acc || curr % 2 === 0, false);
console.log(hasEven); // trueThe superpower: With reduce, you can do all these transformations in asingle pass through the array, while chaining map, filter, andfind would require multiple passes. This means faster code for large datasets!
Frequency Counting with Reduce
One of the most common and powerful uses of reduce is building frequency maps β counting how many times each element appears in an array.
// Count occurrences in an array
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple', 'grape'];
const fruitCount = fruits.reduce((count, fruit) => {
count[fruit] = (count[fruit] || 0) + 1;
return count;
}, {});
console.log(fruitCount);
// { apple: 3, banana: 2, orange: 1, grape: 1 }
// The magic line explained:
// count[fruit] = (count[fruit] || 0) + 1;
// If the fruit exists, increment it; if not, start at 0 then add 1
// Count characters in a string
const 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 }
// Group objects by property
const 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' },
{ name: 'Eve', age: 35, city: 'LA' }
];
const groupedByCity = people.reduce((groups, person) => {
const city = person.city;
if (!groups[city]) groups[city] = [];
groups[city].push(person);
return groups;
}, {});
console.log(groupedByCity);
// {
// NYC: [{name:'Alice'}, {name:'Charlie'}],
// LA: [{name:'Bob'}, {name:'Eve'}],
// Chicago: [{name:'Diana'}]
// }
// Histogram with ranges
const ages = [22, 25, 31, 28, 35, 42, 27, 33, 29, 41, 38, 45, 19, 21];
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);
// { '10-19': 2, '20-29': 6, '30-39': 4, '40-49': 2 }JSON Joining (SQL-like Joins) with Reduce
This is where reduce truly shines β performing database-style joins on JSON data without needing a database! You can create lookup tables and join data in O(n) time instead of O(nΒ²).
// Sample data (like database tables)
const customers = [
{ id: 1, name: 'Alice', email: 'alice@email.com', city: 'NYC' },
{ id: 2, name: 'Bob', email: 'bob@email.com', city: 'LA' },
{ id: 3, name: 'Charlie', email: 'charlie@email.com', city: 'Chicago' }
];
const orders = [
{ id: 101, customerId: 1, product: 'Laptop', amount: 1200 },
{ id: 102, customerId: 2, product: 'Mouse', amount: 25 },
{ id: 103, customerId: 1, product: 'Keyboard', amount: 80 },
{ id: 104, customerId: 3, product: 'Monitor', amount: 350 },
{ id: 105, customerId: 2, product: 'Headphones', amount: 150 }
];
// Step 1: Create a lookup map (O(n))
const customerMap = customers.reduce((map, customer) => {
map[customer.id] = customer;
return map;
}, {});
console.log(customerMap);
// {
// 1: { id:1, name:'Alice', email:'alice@email.com', city:'NYC' },
// 2: { id:2, name:'Bob', email:'bob@email.com', city:'LA' },
// 3: { id:3, name:'Charlie', email:'charlie@email.com', city:'Chicago' }
// }
// Step 2: Join orders with customers (O(m))
const enrichedOrders = orders.reduce((result, order) => {
const customer = customerMap[order.customerId];
result.push({
orderId: order.id,
customerName: customer?.name,
customerEmail: customer?.email,
customerCity: customer?.city,
product: order.product,
amount: order.amount,
// Add any derived fields
orderTotal: order.amount,
customerLocation: customer?.city
});
return result;
}, []);
console.log(enrichedOrders);
// [
// { orderId:101, customerName:'Alice', product:'Laptop', amount:1200, ... },
// { orderId:102, customerName:'Bob', product:'Mouse', amount:25, ... },
// { orderId:103, customerName:'Alice', product:'Keyboard', amount:80, ... },
// { orderId:104, customerName:'Charlie', product:'Monitor', amount:350, ... },
// { orderId:105, customerName:'Bob', product:'Headphones', amount:150, ... }
// ]
// LEFT JOIN equivalent (all orders, even if customer missing)
const ordersWithMissing = [
...orders,
{ id: 106, customerId: 99, product: 'Unknown', amount: 0 } // No such customer
];
const leftJoin = ordersWithMissing.reduce((result, order) => {
const customer = customerMap[order.customerId] || { name: 'Unknown', email: 'N/A', city: 'N/A' };
result.push({ ...order, customerName: customer.name });
return result;
}, []);
// INNER JOIN equivalent (only orders with valid customers)
const innerJoin = orders.reduce((result, order) => {
const customer = customerMap[order.customerId];
if (customer) { // Only include if customer exists
result.push({ ...order, customerName: customer.name });
}
return result;
}, []);
// GROUP BY with aggregates (total spent per customer)
const customerSpending = orders.reduce((result, order) => {
const customerId = order.customerId;
if (!result[customerId]) {
result[customerId] = {
customerId,
customerName: customerMap[customerId]?.name,
totalSpent: 0,
orders: []
};
}
result[customerId].totalSpent += order.amount;
result[customerId].orders.push(order.id);
return result;
}, {});
console.log(Object.values(customerSpending));
// [
// { customerId:1, customerName:'Alice', totalSpent:1280, orders:[101,103] },
// { customerId:2, customerName:'Bob', totalSpent:175, orders:[102,105] },
// { customerId:3, customerName:'Charlie', totalSpent:350, orders:[104] }
// ]
// Nested structures (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,
orderCount: customerOrders.length,
totalSpent: customerOrders.reduce((sum, o) => sum + o.amount, 0)
});
return result;
}, []);
console.log(customersWithOrders);
// [
// { id:1, name:'Alice', orders:2, totalSpent:1280, ... },
// { id:2, name:'Bob', orders:2, totalSpent:175, ... },
// { id:3, name:'Charlie', orders:1, totalSpent:350, ... }
// ]Merging Data with Reduce
Reduce is perfect for merging multiple data sources, arrays, or objects into a single structure.
// Merging objects (like Object.assign but with reduce)
const objects = [
{ a: 1, b: 2 },
{ b: 3, c: 4 },
{ d: 5, e: 6 }
];
const mergedObject = objects.reduce((result, obj) => {
return { ...result, ...obj };
}, {});
console.log(mergedObject);
// { a: 1, b: 3, c: 4, d: 5, e: 6 }
// Flatten an array of arrays
const nestedArrays = [[1, 2], [3, 4], [5, 6], [7, 8]];
const flattened = nestedArrays.reduce((flat, arr) => {
return flat.concat(arr);
}, []);
console.log(flattened);
// [1, 2, 3, 4, 5, 6, 7, 8]
// Flatten with custom logic (only evens)
const flattenedEvens = nestedArrays.reduce((flat, arr) => {
return flat.concat(arr.filter(num => num % 2 === 0));
}, []);
console.log(flattenedEvens);
// [2, 4, 6, 8]
// Deep flatten (handles any nesting level)
const deepNested = [1, [2, [3, 4], 5], 6, [7, [8, [9]]]];
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, 7, 8, 9]
// Merge with custom logic (sum quantities for same id)
const items = [
{ id: 1, quantity: 2 },
{ id: 2, quantity: 3 },
{ id: 1, quantity: 1 },
{ id: 3, quantity: 4 },
{ id: 2, quantity: 2 }
];
const mergedItems = items.reduce((result, item) => {
const existing = result.find(i => i.id === item.id);
if (existing) {
existing.quantity += item.quantity;
} else {
result.push({ ...item });
}
return result;
}, []);
console.log(mergedItems);
// [{ id:1, quantity:3 }, { id:2, quantity:5 }, { id:3, quantity:4 }]
// Union of arrays (unique values)
const arrays = [
[1, 2, 3, 4],
[3, 4, 5, 6],
[5, 6, 7, 8]
];
const union = arrays.reduce((result, arr) => {
arr.forEach(item => {
if (!result.includes(item)) {
result.push(item);
}
});
return result;
}, []);
console.log(union);
// [1, 2, 3, 4, 5, 6, 7, 8]
// Intersection of arrays (common to all)
const intersection = arrays.reduce((result, arr) => {
return result.filter(item => arr.includes(item));
});
console.log(intersection);
// [] (no number appears in all three)
// Better intersection example
const arrays2 = [
[1, 2, 3, 4, 5],
[3, 4, 5, 6, 7],
[5, 6, 7, 8, 9]
];
const intersection2 = arrays2.reduce((result, arr) => {
return result.filter(item => arr.includes(item));
});
console.log(intersection2);
// [5] (only 5 appears in all three)Advanced Reduce Patterns
Once you master the basics, you can use reduce for sophisticated data transformations.
// Running averages
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 });
console.log(runningAverages.averages);
// [10, 15, 20, 25, 30]
// Pipeline of functions (function composition)
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);
// Chunk array into groups
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,8,9], 3));
// [[1,2,3], [4,5,6], [7,8,9]]
// Index array by property (for O(1) lookup)
const users = [
{ id: 'abc123', name: 'Alice', age: 30 },
{ id: 'def456', name: 'Bob', age: 25 },
{ id: 'ghi789', name: 'Charlie', age: 35 }
];
const userMap = users.reduce((map, user) => {
map[user.id] = user;
return map;
}, {});
console.log(userMap.def456);
// { id: 'def456', name: 'Bob', age: 25 }
// O(1) lookup vs O(n) with find()!
// Conditional accumulation
const transactions = [
{ type: 'credit', amount: 100 },
{ type: 'debit', amount: 30 },
{ type: 'credit', amount: 50 },
{ type: 'debit', amount: 20 },
{ type: 'credit', amount: 75 }
];
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: 175, credits: [100,50,75], debits: [30,20] }
// Remove duplicates by key
const itemsWithDupes = [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 1, name: 'Apple' },
{ id: 3, name: 'Orange' },
{ id: 2, name: 'Banana' }
];
const uniqueById = itemsWithDupes.reduce((unique, item) => {
if (!unique.some(u => u.id === item.id)) {
unique.push(item);
}
return unique;
}, []);
console.log(uniqueById);
// [{id:1,name:'Apple'}, {id:2,name:'Banana'}, {id:3,name:'Orange'}]
// More efficient with Map
const uniqueMap = itemsWithDupes.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'}]Common Mistakes & Best Practices
Complete Reduce Reference
/**
* Complete Reduce Patterns Reference
* The Swiss Army Knife of JavaScript
*/
// 1. FREQUENCY COUNTING
const count = (arr) => arr.reduce((acc, item) => {
acc[item] = (acc[item] || 0) + 1;
return acc;
}, {});
// 2. GROUPING
const groupBy = (arr, keyFn) => arr.reduce((acc, item) => {
const key = keyFn(item);
if (!acc[key]) acc[key] = [];
acc[key].push(item);
return acc;
}, {});
// 3. LOOKUP MAP
const indexBy = (arr, keyFn) => arr.reduce((acc, item) => {
acc[keyFn(item)] = item;
return acc;
}, {});
// 4. MERGING OBJECTS
const merge = (objects) => objects.reduce((acc, obj) => ({ ...acc, ...obj }), {});
// 5. FLATTENING
const flatten = (arr) => arr.reduce((acc, item) => acc.concat(item), []);
// 6. UNIQUE VALUES
const unique = (arr) => arr.reduce((acc, item) =>
acc.includes(item) ? acc : [...acc, item], []);
// 7. CHUNKING
const chunk = (arr, size) => arr.reduce((acc, _, i) =>
i % size ? acc : [...acc, arr.slice(i, i + size)], []);
// 8. PIPELINE COMPOSITION
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
// 9. RUNNING AVERAGE
const runningAverage = (arr) => arr.reduce((acc, val, i) => {
const sum = acc.sum + val;
acc.averages.push(sum / (i + 1));
acc.sum = sum;
return acc;
}, { sum: 0, averages: [] }).averages;
// 10. SQL-like JOIN
const join = (left, right, leftKey, rightKey, mergeFn) => {
const rightMap = right.reduce((acc, item) => {
const key = item[rightKey];
if (!acc[key]) acc[key] = [];
acc[key].push(item);
return acc;
}, {});
return left.reduce((acc, leftItem) => {
const matches = rightMap[leftItem[leftKey]] || [];
matches.forEach(rightItem => {
acc.push(mergeFn(leftItem, rightItem));
});
return acc;
}, []);
};Reduce is not just a method β it's a mindset. Once you master it, you'll see opportunities to use it everywhere. From simple sums to complex JSON joins, from frequency counting to data pipelines, reduce is the one method that can do it all.
π― The Reduce Master's Cheat Sheet
- Counting:
acc[key] = (acc[key] || 0) + 1 - Grouping:
if(!acc[key]) acc[key]=[]; acc[key].push(item) - Lookup maps:
acc[item.id] = item - Summing:
acc + current(start with 0) - Building arrays:
acc.push(current); return acc - Merging objects:
...acc, ...current - Conditional logic:
if(condition) acc.push(current)
Remember: The accumulator's type determines what you can build. Choose your initial value wisely, always return the accumulator, and you'll unlock the full power of reduce!