Still grouping arrays with reduce? Use Object.groupBy

Object.groupBy splits any array into groups in one line, based on a value a callback returns, and gives you an object with no prototype. When the key is an object rather than a string, use Map.groupBy.

Published 3 min read#Web development#Developer tools

A reduce call several lines long to split orders by status, or a whole library loaded for one function called groupBy. Many projects still have both, even though JavaScript itself now has the function. In this article you will replace that code with one line, learn the small trap in the result, and see when you need its sibling.

What is Object.groupBy?

Object.groupBy is a built-in JavaScript function that takes an array and a callback, and puts each item in a group named after the value the callback returned for it.

The Object.groupBy page on MDN says it accepts any iterable, such as an array, and returns an object with a property for each group, each holding an array of that group's items. It has been available across browsers since March 2024.

What did the code look like before, and what does it look like now?

Before, you wrote reduce and built the object by hand; now you write one line that says exactly what you want: group by status.

Take a list of orders:

const orders = [
  { id: 1, status: "paid", total: 40 },
  { id: 2, status: "pending", total: 15 },
  { id: 3, status: "paid", total: 22 },
];

This is how grouping by status used to look:

const byStatus = orders.reduce((groups, order) => {
  (groups[order.status] ??= []).push(order);
  return groups;
}, {});

And this is how it looks now:

const byStatus = Object.groupBy(orders, (order) => order.status);

The result is the same: byStatus.paid holds the two orders with status paid, and byStatus.pending holds the other one. The callback also receives the item's index as a second argument, so you can group by anything you compute, like "needs restock" when the quantity is low.

Why does hasOwnProperty fail on the result?

hasOwnProperty fails on the result of Object.groupBy because the result is an object with no prototype, so it inherits none of the methods on Object.prototype.

The same MDN page says the return value is a null-prototype object, so older code that checks keys with hasOwnProperty breaks:

byStatus.hasOwnProperty("paid"); // TypeError
Object.hasOwn(byStatus, "paid"); // true

The Object.hasOwn page on MDN explains that a null-prototype object cannot reach hasOwnProperty, and recommends Object.hasOwn because it works on such objects. When you read a group that may not exist, write byStatus.refunded ?? [] so you do not get undefined.

When do you need Map.groupBy?

You need Map.groupBy when the group key is an object rather than a string, such as the customer itself instead of their name, because keys of a plain object are turned into strings.

The MDN page on Object.groupBy says to use it when group names can be represented by strings, and to use Map.groupBy for any other key. The Map.groupBy page on MDN says the result is a Map, and you reach each group with the same object you used as the key, not another object that happens to have the same properties:

const ali = { name: "Ali" };
const sara = { name: "Sara" };
const sales = [
  { customer: ali, total: 40 },
  { customer: sara, total: 15 },
  { customer: ali, total: 22 },
];

const byCustomer = Map.groupBy(sales, (sale) => sale.customer);
byCustomer.get(ali);
byCustomer.get({ name: "Ali" }); // undefined

The same page says the items in the groups are the same items as in the original array, not copies: change an order in a group and it changes in the list too.

Where do you start now?

Search your project for a reduce that builds an object of arrays, or for a groupBy import from a library, and replace the first one with Object.groupBy. If the code after it uses hasOwnProperty on the result, switch that to Object.hasOwn in the same commit.

If you need an independent copy of the groups before you change them, see deep copies with structuredClone, and for another built-in that saves you code, see a timeout for fetch with AbortSignal.timeout. And if you are still deciding what to learn next, Developer Roadmap 2026 helps you plan it.

How many reduce calls like this did you find in your project?

FAQ

What does Object.groupBy return for a group with no items?

It returns no key for it at all. Keys exist only for values the callback returned, so reading a missing group gives undefined, and it is safer to add ?? [] when you read it.

Does Object.groupBy copy the items?

No. The groups hold the same objects as the original array, so changing an item in a group changes it in the array too. If you need an independent copy, copy first.

Can I group by more than one field?

Yes. Have the callback return a string made of both fields, like the status and the city with a separator. If you want groups inside groups, call groupBy again on each group.

Share

More articles

  • 3 min read#App security#Privacy

    Is your password reset link leaking? Set Referrer-Policy

    Browsers send your page address with requests in the Referer header. The Referrer-Policy header decides how much of it goes out, and no-referrer removes it from sensitive pages.

  • 2 min read#App security#Web development

    Can an uploaded text file open as a page? Add nosniff

    The X-Content-Type-Options: nosniff header tells the browser to follow the Content-Type your server sent instead of guessing the type from the content, so a text/plain file with HTML in it stays text.

  • 3 min read#AI#Developer tools

    Know the size of your Claude prompt before you send it

    The count_tokens endpoint in the Claude API tells you how many input tokens a request has before you send it, in the same shape as a message request. Counting is free, with its own rate limit.