---
title: "Still grouping arrays with reduce? Use Object.groupBy"
description: "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."
url: https://ihsanalapsi.dev/articles/javascript-object-groupby
lang: en
date: 2026-11-06
updated: 2026-11-06
tags: ["Web development", "Developer tools"]
author: Ihsan Alapsi (إحسان العبسي)
---
# Still grouping arrays with reduce? Use Object.groupBy

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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy) 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:

```javascript
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:

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

And this is how it looks now:

```javascript
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:

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

The [Object.hasOwn page on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn) 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/groupBy) 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:

```javascript
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](/articles/javascript-structuredclone-deep-copy), and for another built-in that saves you code, see [a timeout for fetch with AbortSignal.timeout](/articles/fetch-timeout-abortsignal). And if you are still deciding what to learn next, [Developer Roadmap 2026](/products/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.
