Open any frontend codebase and search for .reduce. I'll wait.
A meaningful fraction of those hits look like this:
const byStatus = tasks.reduce((acc, task) => {
const key = task.status;
if (!acc[key]) acc[key] = [];
acc[key].push(task);
return acc;
}, {});
Or, if the author has been around long enough, this tighter variant:
const byStatus = tasks.reduce((acc, task) => {
(acc[task.status] ??= []).push(task);
return acc;
}, {});
Both do the same thing: group an array by a key. It's one of the most common data transforms in any frontend codebase — group tasks by status, events by date, errors by code, users by role. You've written it dozens of times because JavaScript never had a built-in name for it.
It does now. And it has been in every major browser since early 2024.
Object.groupBy — the one-liner
const byStatus = Object.groupBy(tasks, task => task.status);
Pass an iterable and a callback. The callback returns the group key for each item. The result is a plain object where each key holds an array of the items that mapped to it.
const tasks = [
{ id: 1, status: 'done', title: 'Ship it' },
{ id: 2, status: 'todo', title: 'Write tests' },
{ id: 3, status: 'done', title: 'Fix the bug' },
{ id: 4, status: 'in-progress', title: 'Review the PR' },
];
const grouped = Object.groupBy(tasks, t => t.status);
// {
// done: [{ id: 1, … }, { id: 3, … }],
// todo: [{ id: 2, … }],
// 'in-progress': [{ id: 4, … }],
// }
The callback can return any value that stringifies to a useful key — a status enum, a date string, a category slug, a computed bucket. Items that map to the same string land in the same array. Items that would map to undefined land under the key "undefined", which is a sign you should add a fallback in the callback.
🎮 Try it yourself
▶️ Open the interactive playground →
Runs right in your browser — poke at it and watch the concept react live.
When you need non-string keys: Map.groupBy
Object.groupBy coerces every key to a string. That covers most cases. If you need to group by something that isn't meaningfully stringifiable — an object reference, a boolean you want distinct from the string "true", or any value where identity matters — Map.groupBy is the variant:
const byPriority = Map.groupBy(tasks, task => task.priority > 5);
byPriority.get(true); // high-priority tasks
byPriority.get(false); // everything else
The result is a real Map, so you access values with .get() and iterate with .entries(). Keys are the exact values your callback returned — no stringification, no coercion, no surprises.
A real-world example: the status board
Before groupBy, building a Kanban-style board from a flat API response involved one of two patterns. Either a filter per lane:
const lanes = ['todo', 'in-progress', 'review', 'done'];
const grouped = Object.fromEntries(
lanes.map(status => [status, tasks.filter(t => t.status === status)])
);
That walks tasks once per lane — four passes for four columns. Or a reduce — one pass, but four lines of bookkeeping per developer who reads it.
With Object.groupBy:
const grouped = Object.groupBy(tasks, t => t.status);
const lanes = ['todo', 'in-progress', 'review', 'done'];
// `grouped` already has everything; `lanes` just controls render order
One pass. No ceremony. The intent is in the function name.
One subtle thing about the returned object
Object.groupBy returns an object with a null prototype — Object.create(null). It has no inherited toString, hasOwnProperty, or any of the usual Object.prototype methods. It's a pure data dictionary.
For most real uses this is exactly right: no prototype key collisions, no need for the hasOwnProperty dance before accessing a key. If downstream code needs a normal prototype — say, you're serializing it with a library that calls toString — wrap it: Object.assign({}, grouped). But in practice you rarely need to.
Support
Object.groupBy is Baseline 2024: Chrome 117, Firefox 119, Safari 17.4, Node.js 21. If your targets include browsers from the last two years you can use it today without a polyfill. For older targets a one-liner shim is trivial, but at this point you're more likely to just set a browserslist target that excludes the handful of users still on pre-2024 releases.
🧠 Test yourself
Think it clicked? Take the 6-question quiz →
Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.
The takeaway
Object.groupBy isn't a convenience — it's the word for what you've been spelling out in four lines of reduce. Find the grouping reduces in your codebase, replace them, and delete whatever groupBy helper you wrote to avoid writing them inline. The operation has a name and a native implementation now; there's nothing left to carry.
Top comments (1)
How does Object.groupBy handle nested objects, I've had issues with reduce in those cases. Would love to hear your thoughts on this.