Object.groupBy()
The built-in version of the reduce everyone wrote by hand. Its null prototype is a deliberate safety feature, and the thing most likely to catch you out.
Demo
Each item is passed to the callback and filed under whatever string comes back, preserving the original order within each bucket. A key only appears if something landed in it — the second case has no 'even' property at all, rather than an empty array, so code that reads result.even must handle undefined. An empty input gives an empty object. What the output cannot show is that this object has a null prototype: it has no toString, no hasOwnProperty, and does not inherit anything from Object.prototype.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| items | iterable | yes | Any iterable — an array, a Set, a generator. |
| callback | Function | yes | Called as callback(element, index). Its return value becomes the group key, coerced to a string — so returning an object gives you one bucket named "[object Object]". |
Return value
object — An object with a NULL prototype, mapping each key the callback returned to an array of the items that produced it. Insertion order follows first appearance, subject to the usual integer-key rule.
Common patterns
const byStatus = Object.groupBy(orders, o => o.status);
const byDate = Map.groupBy(events, e => e.date);
const pending = byStatus.pending ?? [];
Examples
Pitfalls
String(Object.groupBy([1], () => "a"))
Object.assign({}, Object.groupBy([1], () => "a"))
Object.groupBy([1], () => "odd").even.length
(Object.groupBy([1], () => "odd").even ?? []).length
Object.groupBy([1, 2], x => ({id: x}))
Map.groupBy([1, 2], x => ({id: x}))
Object.groupBy(xs, f)
xs.reduce((acc, x) => { (acc[f(x)] ??= []).push(x); return acc; }, {})
When to use
- Bucketing records by a string field
- Partitioning a list into named categories
- Replacing a hand-written grouping reduce
- Keys are not strings → Map.groupBy, which preserves them
- You need the result to behave like a normal object → spread it into one
- You want counts rather than the items → reduce, or map over the groups
- Targeting runtimes older than 2024 → the reduce form
Notes
FAQ
So that a group key can never collide with something inherited. If the callback returns "toString" or "__proto__", a normal object would either shadow a method or — historically — corrupt the prototype. A null-prototype object has nothing to collide with.
Object.getPrototypeOf(Object.groupBy([1], () => "a")); // null