Why JavaScript’s most misunderstood array method is secretly your Swiss Army knife for transforming data, not just adding it up.

Introduction
Ask most developers what reduce
is for, and you’ll probably hear: “summing an array of numbers.” And yes, that’s the canonical example.
But here’s the thing: reduce
is way more powerful. It can build objects, flatten arrays, group data, run async flows, and even mimic other array methods like map
and filter
. In fact, if JavaScript only gave us reduce
, we could reconstruct most of the other higher-order functions we love.
The problem? Many devs (especially juniors) avoid reduce
because it looks weird at first: that callback with an “accumulator” feels abstract. Once you get past that, you’ll realize it’s one of the most expressive and flexible tools in the language.
This article will show you how to go beyond numbers with reduce
— with real-world patterns, copy-ready snippets, and gotchas to avoid.
1. The Basics Refresher
const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, n) => acc + n, 0);
console.log(sum); // 10
- acc → the accumulator (what we’re building up)
- n → the current value
- 0 → the initial accumulator
That’s the textbook case. Let’s move beyond it.
2. Building Objects From Arrays
Index an array by key
const users = [
{ id: 1, name: "Rahmat" },
{ id: 2, name: "Umar" },
];
const byId = users.reduce((acc, u) => {
acc[u.id] = u;
return acc;
}, {});
console.log(byId);
// { '1': { id: 1, name: 'Rahmat' }, '2': { id: 2, name: 'Umar' } }
👉 Why: Quick lookup tables beat repeated .find()
calls.
Grouping items
const posts = [
{ category: "JS", title: "Closures" },
{ category: "CSS", title: "Flexbox" },
{ category: "JS", title: "Promises" },
];
const grouped = posts.reduce((acc, p) => {
acc[p.category] ??= [];
acc[p.category].push(p.title);
return acc;
}, {});
console.log(grouped);
// { JS: ["Closures", "Promises"], CSS: ["Flexbox"] }
👉 Why: Perfect for charts, dashboards, or filtering in React apps.
3. Rebuilding Other Array Methods
Re-implement map
const nums = [1, 2, 3];
const doubled = nums.reduce((acc, n) => {
acc.push(n * 2);
return acc;
}, []);
console.log(doubled); // [2, 4, 6]
Re-implement filter
const nums = [1, 2, 3, 4];
const evens = nums.reduce((acc, n) => {
if (n % 2 === 0) acc.push(n);
return acc;
}, []);
console.log(evens); // [2, 4]
👉 Why: Shows the raw power — reduce
can simulate higher-order functions. In practice, use map
/filter
for clarity, but knowing this makes you dangerous.
4. Flattening Arrays
One level deep
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flat); // [1,2,3,4,5]
Deep flatten (recursive reduce)
function deepFlatten(arr) {
return arr.reduce((acc, val) => {
return acc.concat(Array.isArray(val) ? deepFlatten(val) : val);
}, []);
}
console.log(deepFlatten([1, [2, [3, 4]], 5]));
// [1, 2, 3, 4, 5]
👉 Why: Before flat(Infinity)
existed, this was the go-to trick.
5. Chaining Transformations
You can stack multiple transformations into a single pass:
const nums = [1, 2, 3, 4, 5];
const result = nums.reduce((acc, n) => {
if (n % 2 === 0) acc.push(n * 10);
return acc;
}, []);
console.log(result); // [20, 40]
👉 Why: Combines filter
+ map
in one loop — fewer allocations, sometimes faster for hot paths.
6. Reducing to Strings
Join with logic
const words = ["JS", "is", "fun"];
const sentence = words.reduce((acc, w, i) => {
return acc + (i > 0 ? " " : "") + w;
}, "");
console.log(sentence); // "JS is fun"
👉 Why: Gives you control beyond .join()
, e.g., adding commas, “and”, or custom separators.
7. Handling Async with reduce
You can chain async operations sequentially.
async function run() {
const urls = ["/a", "/b", "/c"];
const results = await urls.reduce(async (accP, url) => {
const acc = await accP;
const data = await fetch(url).then(r => r.text());
acc.push(data);
return acc;
}, Promise.resolve([]));
console.log(results);
}
👉 Why: Forces sequential order — useful when requests depend on each other.
8. Counting & Frequency Maps
Word frequency
const words = ["apple", "banana", "apple", "orange", "banana", "apple"];
const counts = words.reduce((acc, w) => {
acc[w] = (acc[w] || 0) + 1;
return acc;
}, {});
console.log(counts);
// { apple: 3, banana: 2, orange: 1 }
👉 Why: Great for logs, analytics, and quick stats.
9. Reduce in React & State Management
Imagine you get API data as a flat list and want to group it for UI.
const items = [
{ section: "A", value: 1 },
{ section: "B", value: 2 },
{ section: "A", value: 3 },
];
const sections = items.reduce((acc, item) => {
acc[item.section] ??= [];
acc[item.section].push(item);
return acc;
}, {});
// Later in JSX
Object.entries(sections).map(([section, items]) => (
<Section key={section} title={section} items={items} />
));
👉 Why: reduce
shines in data-to-UI transformations.
10. Gotchas and Pro Tips
- Always set an initial value. Without it,
reduce
uses the first element, leading to weird bugs. - Prefer readability over cleverness. Sometimes
map
+filter
is clearer. - Use TypeScript generics. Helps ensure accumulator and return types line up.
Example with TypeScript:
const sumLengths = (arr: string[]) =>
arr.reduce<number>((acc, str) => acc + str.length, 0);
Conclusion
reduce
isn’t just about summing numbers — it’s about reducing a list of things into one result, whatever that result is: an object, a string, a promise chain, or a React UI structure.
Once you reframe reduce
as “transform many into one,” the patterns click. Use it to group, flatten, build lookup tables, run async sequences, or roll your own utilities.
Call to Action
What’s the coolest or strangest way you’ve used reduce
?
Drop it in the comments — and share this with a teammate who still thinks reduce
is only for math.
Leave a Reply