Articles

Object.groupBy() in JavaScript, Explained

Object.groupBy() buckets array items by a key you compute, replacing the reduce() boilerplate developers have written for years. How it works.

Takina Takina · · 4 min read
Code on a screen in an editor

Object.groupBy() is a built-in JavaScript static method that takes an array and a callback, and returns a plain object whose keys are the callback’s return values and whose values are arrays of the items that produced each key. It replaces one of the most commonly hand-rolled patterns in everyday JavaScript — bucketing a list of items by some derived property — with a single standard-library call instead of a reduce() you write from scratch every time.

The problem it solves

Grouping array items by a key is a routine need: group orders by status, group users by role, group log lines by severity. Before Object.groupBy(), this meant writing the same few lines of reduce() boilerplate over and over:

const orders = [
  { id: 1, status: "shipped" },
  { id: 2, status: "pending" },
  { id: 3, status: "shipped" },
  { id: 4, status: "cancelled" },
];

const grouped = orders.reduce((acc, order) => {
  const key = order.status;
  (acc[key] ??= []).push(order);
  return acc;
}, {});

It works, but it’s easy to get slightly wrong (forgetting to initialize the array, mutating the accumulator unsafely) and it obscures the actual intent — “group these by status” — behind manual accumulator plumbing.

How Object.groupBy() works

Object.groupBy() does exactly the same thing, natively:

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

// {
//   shipped: [{ id: 1, ... }, { id: 3, ... }],
//   pending: [{ id: 2, ... }],
//   cancelled: [{ id: 4, ... }]
// }

The callback receives each element and, like Array.prototype.map, its index as a second argument — useful if the grouping key needs to depend on position rather than just the item’s own data. The method works on any iterable, not just arrays, which means it also accepts the results of generators, Map values, or Sets directly.

One detail worth internalizing: the returned object has a null prototype (Object.create(null)), not the usual Object.prototype. That means it won’t have inherited methods like toString or hasOwnProperty, and — more importantly — it avoids a subtle bug class where a grouping key happens to collide with a built-in property name like constructor or __proto__. With a normal object, grouping by a key named "__proto__" can silently do something very different from what you’d expect; with the null-prototype result of Object.groupBy(), it just becomes an ordinary own property.

Map.groupBy(): the same idea, keyed by anything

Plain-object keys are always coerced to strings, which is fine for grouping by a string like status but breaks down if the natural grouping key is a number, an object, or anything else you want to keep as its real type. Map.groupBy() solves this by returning a Map instead of a plain object, preserving the key’s original type:

const byId = Map.groupBy(items, (item) => item.categoryObject);
// keys are the actual categoryObject references, not stringified

Use Object.groupBy() when the keys are naturally strings (or you’re fine with them being coerced to strings) and you want a plain object you can access with dot or bracket notation. Use Map.groupBy() when keys need to preserve their original type, or when you specifically want Map’s other properties — guaranteed insertion order, an accurate .size, and iteration methods.

Grouping vs filtering and reducing

Object.groupBy() sits alongside the other array-transformation methods most JavaScript developers already reach for, and it’s worth being clear about when each one is the right tool. See Array map, filter, and reduce for the broader family this joins:

  • Use .filter() when you want a single subset matching a condition.
  • Use .map() when you’re transforming each element one-to-one without changing the count.
  • Use .reduce() when you’re collapsing an array into some other accumulated shape that isn’t a simple grouping.
  • Use Object.groupBy() / Map.groupBy() specifically when the goal is partitioning items into buckets by a derived key — it’s a reduce() with the accumulator pattern already solved for you.

It also pairs naturally with array and object destructuring when you know the grouping keys ahead of time:

const { shipped = [], pending = [], cancelled = [] } = Object.groupBy(
  orders,
  (o) => o.status,
);

A note on browser and runtime support

Object.groupBy() and Map.groupBy() are recent additions to the language (part of the ECMAScript 2024 specification), so if you’re targeting environments older than a couple of years — certain enterprise browser deployments, older Node.js versions, or some embedded JS engines — check your actual support matrix before relying on them, and fall back to the reduce() pattern or a small polyfill where needed. In current evergreen browsers and current Node.js LTS releases, both methods are available without a flag or import.

They’re comparable in spirit to other recently added array conveniences like the Set methods for union and intersection — small standard-library additions that replace patterns developers were already writing by hand, rather than introducing new concepts to learn.

The takeaway

Object.groupBy() takes an array and a key-producing callback and returns items bucketed by that key, replacing the reduce() boilerplate developers have written for this exact task for years. It returns a null-prototype object, which sidesteps prototype-pollution footguns that plain objects are prone to when a grouping key collides with a built-in property name. Reach for Map.groupBy() instead when the natural grouping key isn’t a string and you need it preserved as its original type, or when you want Map’s ordering and iteration guarantees.

Takina Takina · · 4 min read

Svelte 5 Runes Explained

Svelte 5 runes like $state and $derived replace the old reactive-assignment magic with explicit function calls that work anywhere in a file.

#JavaScript #Frameworks #Web Development
Takina Takina · · 4 min read

Shallow Copy vs Deep Copy in JavaScript

A shallow copy duplicates an object's top-level properties but shares nested references; a deep copy duplicates everything recursively. How to do each.

#JavaScript #Web Development #Frontend