Group array items by a computed key
Canonical URL: https://devexamples.com/javascript/group-array-items-by-a-key/
The problem
Repeated `find` and `filter` calls over the same list turn one cheap load into a slow render, and hand-written accumulator objects tend to collide on keys that need normalising first.
Short answer
Use Object.groupBy with a key function, or a single reduce pass when you need an insertion order guarantee or a Map for non-string keys.
Grouping is a two-line change that removes an accidental quadratic loop.
const orders = [
{ id: 'a1', customer: 'acme', total: 120, placedAt: '2026-09-02' },
{ id: 'b7', customer: 'acme', total: 80, placedAt: '2026-09-05' },
{ id: 'c3', customer: 'northwind', total: 410, placedAt: '2026-09-11' },
];
const byCustomer = Object.groupBy(orders, (order) => order.customer);
// { acme: [ {a1}, {b7} ], northwind: [ {c3} ] }// When the key is not a string, or the caller needs the groups in a known order,
// accumulate in a Map instead.
function groupBy(list, keyOf, sortItems) {
const groups = new Map();
for (const item of list) {
const key = keyOf(item);
const bucket = groups.get(key);
if (bucket) bucket.push(item);
else groups.set(key, [item]);
}
if (sortItems) for (const bucket of groups.values()) bucket.sort(sortItems);
return groups;
}
const byMonth = groupBy(
orders,
(order) => order.placedAt.slice(0, 7),
(left, right) => left.total - right.total,
);Explanation
Object.groupBy calls the callback once per element and appends the element to the array stored
under the returned key, so a pass that would otherwise need a filter per group becomes a single
traversal. The callback’s return value is converted with ToPropertyKey, meaning numbers and
booleans become usable keys, and a returned null or undefined becomes the literal string keys
"null" and "undefined" rather than a missing bucket. That conversion is the most common source
of surprise: order.customerId of 0 and '0' land in the same group.
The returned object has a null prototype. That protects you from a key like __proto__ or
constructor colliding with inherited members — the ordinary accumulator bug where a grouping key
named toString silently reads a function instead of a bucket — but it also means the result has
no hasOwnProperty, so use Object.hasOwn(groups, key) and prefer Object.entries or
Object.keys when you iterate.
The Map variant exists for the cases Object.groupBy cannot express: keys that must stay
objects or numbers without string coercion, group order that must follow first insertion rather
than key order, and per-group sorting done in the same pass. It also keeps the accumulator
reusable when the caller supplies the comparator.
Parameters
list(array, required): records to group.keyOf(function, required): returns the key for one record; called once per element.sortItems(function, optional): comparator applied inside each group after accumulation.
Expected output
Object.groupBy yields an object whose values are arrays of the original references, in input
order. The Map helper yields the same arrays keyed by the raw key value.
Common mistakes
- Reaching for
reducewith a{}seed and no prototype protection when keys come from user data. - Assuming a key of
0stays numeric; string coercion means the lookup key must also be coerced.
Caveats
Object.groupByrequires a browser generation from 2024 onward; theMaphelper runs anywhere.- Grouping keeps references to the original items, so a group array is not a snapshot of the record.
Examples that treat this as a prerequisite
Derived at build time from the editorial graph; no edge is invented here.
- Normalize an API payload into a typed list
Grouping rows by a key first makes the shape this function returns concrete rather than theoretical.
Related examples
Editorial links first, then deterministic same-task or same-topic candidates. Tags and shared language alone never qualify a candidate.
Related
Normalize an API payload into a typed listThe same reshape expressed with a declared output type.
Related
Transform server records into the view model a component rendersGrouping is one specific shape change among the view-model mappings.
Same task or topic
Narrow an unknown API response with type guardsSolves the Transform data task
References
- Object.groupBy()(opens in a new tab) — MDN. Documents the callback contract and the null-prototype result object.
- ECMAScript Array Grouping proposal(opens in a new tab) — Ecma TC39. Specifies the grouping semantics and why the result has no prototype.
Source page: https://devexamples.com/javascript/group-array-items-by-a-key/