Object.groupBy()

Groups the items of an iterable into a null-prototype object keyed by a callback's return value.

Signature
Object.groupBy(items, callbackFn)
  • Added in JavaScript ES2024

Parameters

ParameterTypeDescription
$itemsIterableAn iterable, such as an Array, whose elements will be grouped.
$callbackFnFunctionA function called for each element as callbackFn(element, index); its return value is coerced to a string and used as the group key.

Return value

ObjectA null-prototype object with one property per group, each holding an array of the elements in that group.

Errors

Throws a TypeError if items is not iterable or callbackFn is not callable.

Object.groupBy() replaces the reduce() boilerplate everyone writes for bucketing a list. It walks the iterable once, calls the callback for each element, and appends that element to the array under the returned key.

The null prototype

The result is created with no prototype, so it has no inherited members at all: no toString, no hasOwnProperty, no constructor. That is deliberate — group keys come from data, and a group literally named "constructor" or "__proto__" must not collide with anything. The cost is that result.hasOwnProperty(k) throws; use Object.hasOwn() instead, and expect a console to print it as [Object: null prototype].

What trips people up

  • Keys are coerced to strings. Grouping by a number gives "1"; grouping by objects collapses them all into "[object Object]". When the key should stay a real value, use Map.groupBy().
  • Only groups that actually occur exist. Accessing an absent group yields undefined, not an empty array — reach for ?? [].
  • Elements are shared by reference with the source; the arrays are new, the items are not copies.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Bucket records by status or category before rendering a grouped list
  • Split a batch of results into successes and failures in one pass
  • Tally items per key by grouping and then reading each array's length

Frequently asked questions

Why does hasOwnProperty fail on the result of Object.groupBy?

The returned object has a null prototype, so it inherits no methods at all; use Object.hasOwn(result, key) or the in operator instead.

What is the difference between Object.groupBy and Map.groupBy?

Object.groupBy() coerces every key to a string and returns a null-prototype object, while Map.groupBy() keeps keys as their original values, including objects, and returns a Map.

How do I get a count per group instead of the items?

Group first, then map over the entries, for example Object.fromEntries(Object.entries(groups).map(([k, v]) => [k, v.length])).

More examples

Using the index argument and handling absent groups

The callback receives the index as its second argument, which is why the top group carries one in its key. Groups that no element produced simply do not exist, so distinction is undefined rather than an empty array; ?? [] is the usual guard. JSON.stringify() still works on a null-prototype object.

Example · javascript
const scores = [88, 42, 95, 61, 77];

const byGrade = Object.groupBy(scores, (score, index) => {
  if (score >= 90) return `top (index ${index})`;
  return score >= 60 ? "pass" : "fail";
});

console.log(byGrade.pass);            // [ 88, 61, 77 ]
console.log(byGrade.fail);            // [ 42 ]
console.log(byGrade.distinction);     // undefined — no such group
console.log(byGrade.distinction ?? []); // []
console.log(JSON.stringify(byGrade).slice(0, 30));

Numeric keys become strings

Object keys are always strings, so the numeric lengths are converted and then sorted into ascending integer order. Map.groupBy() keeps the keys as actual numbers in insertion order, which matters when the key is an object or when you need to compare it without parsing.

Example · javascript
const words = ["hi", "tree", "a", "four", "go"];

const byLength = Object.groupBy(words, (w) => w.length);
console.log(Object.keys(byLength));   // [ '1', '2', '4' ] — strings, numeric order
console.log(byLength[2]);             // [ 'hi', 'go' ] — index coerced on lookup

const byLengthMap = Map.groupBy(words, (w) => w.length);
console.log([...byLengthMap.keys()]); // [ 2, 4, 1 ] — real numbers, insertion order
console.log(byLengthMap.get(2));      // [ 'hi', 'go' ]

Discussion

  • Be the first to comment on this lesson.