TIL: Object.groupBy() — JavaScript Finally Has a Native Group By

3 min read

TIL: Object.groupBy() — JavaScript Finally Has a Native Group By

TIL that JavaScript (ES2024) shipped

1Object.groupBy()
— something I'd been reaching for lodash or a manual
1reduce()
to do for years. It's now in Node.js 21+ and all modern browsers, and it's cleaner than anything I'd been writing by hand.

The Old Way

Grouping an array of objects by a property used to mean either pulling in a utility library or writing something like this every time:

1const posts = [
2  { title: 'Hello', category: 'react' },
3  { title: 'World', category: 'nextjs' },
4  { title: 'Hooks 101', category: 'react' },
5];
6
7const grouped = posts.reduce((acc, post) => {
8  const key = post.category;
9  (acc[key] ??= []).push(post);
10  return acc;
11}, {});
12// { react: [...], nextjs: [...] }
13

It works, but it's noisy enough that I always extracted it into a shared helper — boilerplate for something this common.

Object.groupBy()

Now there's a native single-liner:

1const grouped = Object.groupBy(posts, (post) => post.category);
2// {
3//   react: [{ title: 'Hello', … }, { title: 'Hooks 101', … }],
4//   nextjs: [{ title: 'World', … }]
5// }
6

The callback receives

1(element, index)
— the same signature as
1Array.prototype.map
. Whatever it returns becomes the group key. Groups appear in insertion order.

One subtlety: the returned object has a null prototype, so there's no prototype pollution risk. If you iterate it, use

1Object.entries()
or
1Object.keys()
as normal — they still work on null-prototype objects.

Map.groupBy() for Non-String Keys

If you need to group by a non-string key — a number, an object reference, a Date — reach for

1Map.groupBy()
instead:

1const byLength = Map.groupBy(posts, (post) => post.title.length);
2// Map { 5 => [{ title: 'Hello', … }], 5 => [...], 9 => [...] }
3

1Map.groupBy
preserves key identity the way a
1Map
always does, so two objects with the same content but different references are separate keys.

A Real-World React Example

The place I reach for this most often is rendering grouped lists in React:

1const PostsByCategory = ({ posts }) => {
2  const byCategory = Object.groupBy(posts, (p) => p.category);
3
4  return Object.entries(byCategory).map(([category, items]) => (
5    <section key={category}>
6      <h2>{category}</h2>
7      <ul>
8        {items.map((p) => (
9          <li key={p.slug}>{p.title}</li>
10        ))}
11      </ul>
12    </section>
13  ));
14};
15

Before ES2024 this would've been a

1reduce
call followed by
1Object.entries
— same logic, just more lines.

Support and Fallback

1Object.groupBy
ships in Chrome 117+, Firefox 119+, Safari 17.4+, and Node.js 21+. If you need to target something older today, the
1reduce
pattern above is a perfectly readable fallback. No polyfill library required — it's three lines.

The spec is part of TC39's "change Array by copy" family of additions, which also brought us

1Array.prototype.toSorted()
,
1toReversed()
, and
1with()
— all focused on immutable array operations. Worth a look if you haven't seen them.