loke.dev
Header image for The Array.reduce Grouping Pattern Is Now an Anti-Pattern: Meet Object.groupBy

The Array.reduce Grouping Pattern Is Now an Anti-Pattern: Meet Object.groupBy

Stop torturing your code reviewers with complex reducer logic for simple data categorization now that a native solution has arrived.

· 4 min read

The Array.reduce Grouping Pattern Is Now an Anti-Pattern: Meet Object.groupBy

I remember staring at a pull request last year, squinting at a 15-line reduce function that was doing some heavy lifting. It was grouping products by category, but the logic was buried under a pile of bracket notation and ternary operators that made my brain itch.

We’ve all been there. For years, Array.prototype.reduce() was the "cool kid" tool for transforming data. It’s powerful, sure, but using it for simple data grouping has always felt like using a chainsaw to slice a loaf of bread—messy, overkill, and slightly dangerous if you forget to pass that initial {} at the end.

The Ritual of the Reducer

Before we look at the shiny new solution, let’s look at the boilerplate we’ve been forced to write for over a decade. Imagine we have a list of developers, and we want to group them by their primary language.

const developers = [
  { name: 'Sarah', language: 'JavaScript' },
  { name: 'Eddie', language: 'TypeScript' },
  { name: 'Yuki', language: 'JavaScript' },
  { name: 'Alex', language: 'Rust' },
  { name: 'Chris', language: 'TypeScript' },
];

const groupedByLanguage = developers.reduce((acc, dev) => {
  const key = dev.language;
  if (!acc[key]) {
    acc[key] = [];
  }
  acc[key].push(dev);
  return acc;
}, {});

/* 
Output:
{
  JavaScript: [{ name: 'Sarah'... }, { name: 'Yuki'... }],
  TypeScript: [{ name: 'Eddie'... }, { name: 'Chris'... }],
  Rust: [{ name: 'Alex'... }]
}
*/

It works. It’s "functional." But it's also high-friction. You have to initialize the accumulator, check for the existence of the key, push the data, and—crucially—remember to return the accumulator. Forget that last return and your entire data structure vanishes into the void of undefined.

Enter Object.groupBy

The JavaScript gods (the TC39 committee) finally heard our sighs. Object.groupBy is a static method that handles this exact pattern natively. It’s cleaner, it’s declarative, and it tells the next developer exactly what you're doing without them having to parse your reducer logic.

Here is that same grouping logic using the new syntax:

const developers = [
  { name: 'Sarah', language: 'JavaScript' },
  { name: 'Eddie', language: 'TypeScript' },
  { name: 'Yuki', language: 'JavaScript' },
  { name: 'Alex', language: 'Rust' },
  { name: 'Chris', language: 'TypeScript' },
];

const groupedByLanguage = Object.groupBy(developers, (dev) => dev.language);

That’s it. One line. No if checks, no empty objects dangling at the bottom, no accidental mutations.

Why This Isn't Just "Syntactic Sugar"

You might think, "I'm already comfortable with reduce, why switch?" Aside from making your code look like it belongs in 2024, there are practical benefits:

1. Cognitive Load: When I see reduce, I have to read the whole block to know what's happening. When I see groupBy, I know exactly what the shape of the output will be before I even look at the callback.
2. Intent-Based Programming: Good code should describe *what* it is doing, not just *how*. Object.groupBy is expressive.
3. Static Methods: Unlike most array methods, this is a static method on Object. This is actually a smart design choice because the return value is a plain object, not a new array.

What If You Need Map Keys?

Sometimes, using a string as a key isn't enough. Maybe you want to group things using a complex object as a key (a common requirement in caching or sophisticated data modeling). For that, we have Map.groupBy.

The difference is that Map.groupBy returns a Map instance instead of a plain object.

const inventory = [
  { name: "apples", quantity: 5 },
  { name: "bananas", quantity: 0 },
  { name: "oranges", quantity: 12 },
];

const restockCriteria = { type: 'needs-restock' };
const okCriteria = { type: 'sufficient' };

const inventoryStatus = Map.groupBy(inventory, (item) => {
  return item.quantity < 5 ? restockCriteria : okCriteria;
});

// Now you can access groups using the actual objects as keys
console.log(inventoryStatus.get(restockCriteria)); 
// Output: [{ name: "bananas", quantity: 0 }]

A Real-World Use Case: Sorting by Grade

Let's look at a slightly more complex example. Say you have a list of students with scores, and you want to group them into "Pass" and "Fail" buckets.

const students = [
  { name: 'Alice', score: 92 },
  { name: 'Bob', score: 45 },
  { name: 'Charlie', score: 78 },
  { name: 'David', score: 30 },
];

const results = Object.groupBy(students, ({ score }) => {
  return score >= 50 ? 'pass' : 'fail';
});

console.log(results.fail); 
// [{ name: 'Bob', score: 45 }, { name: 'David', score: 30 }]

Notice how we can destructure the object right in the callback? It’s incredibly readable.

The Elephant in the Room: Support

"Can I use this today?"

Yes. Object.groupBy is part of the Baseline 2023 set. It’s supported in Chrome 117, Firefox 119, and Safari 17.4. If you’re working in a modern Node.js environment (v21+), you're good to go.

If you’re supporting older browsers (looking at you, legacy enterprise apps), you'll need a polyfill (Core-js has you covered), but for most greenfield projects, there’s no reason to stick with the old reduce pattern.

The Verdict

Array.reduce is a powerful tool for folding an array into a single value, like a sum or a complicated state object. But for categorizing data? It's officially retired.

Using Object.groupBy makes your intentions clear, reduces the surface area for bugs, and—most importantly—saves your fellow developers from having to mentally execute a reducer loop just to understand your data structure. Stop torturing your code reviewers and start grouping with style.