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

TIL that JavaScript (ES2024) shipped
— something I'd been reaching for lodash or a manual1Object.groupBy()
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.1reduce()
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
— the same signature as1(element, index)
. Whatever it returns becomes the group key. Groups appear in insertion order.1Array.prototype.map
One subtlety: the returned object has a null prototype, so there's no prototype pollution risk. If you iterate it, use
or1Object.entries()
as normal — they still work on null-prototype objects.1Object.keys()
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
instead:1Map.groupBy()
1const byLength = Map.groupBy(posts, (post) => post.title.length); 2// Map { 5 => [{ title: 'Hello', … }], 5 => [...], 9 => [...] } 3
preserves key identity the way a1Map.groupBy
always does, so two objects with the same content but different references are separate keys.1Map
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
call followed by1reduce
— same logic, just more lines.1Object.entries
Support and Fallback
ships in Chrome 117+, Firefox 119+, Safari 17.4+, and Node.js 21+. If you need to target something older today, the1Object.groupBy
pattern above is a perfectly readable fallback. No polyfill library required — it's three lines.1reduce
The spec is part of TC39's "change Array by copy" family of additions, which also brought us
,1Array.prototype.toSorted()
, and1toReversed()
— all focused on immutable array operations. Worth a look if you haven't seen them.1with()
