Using reduce for More Than Numbers

Posted by

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

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

Your email address will not be published. Required fields are marked *