Coding problem · Easy · Language Idioms

Type-safe groupBy<T, K>()

Easy Language practicals Seen at Microsoft, Shopify, Vercel

Implement a generic groupBy with this signature:

function groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: (item: T) => K): Record<K, T[]>

It groups items by the key returned from keyFn, preserving input order within each group. Keys appear in the result in the order they were first seen.

(Types are erased at runtime, so the tests check behaviour; reviewers check that your types compile under strict.)

Hints

Hint 1

PropertyKey is string | number | symbol — exactly what object keys can be.

Hint 2

Start with {} as Record<K, T[]> and push into (acc[key] ??= []).

Write and run your solution

Loading the editor… (JavaScript is required to run code in your browser.)

Solution and explanation

Show the solution

Time O(n) · Space O(n)

The generic parameters let TypeScript infer T from the array and K from the callback's return type, so groupBy(people, p => p.team) is typed as Record<string, Person[]> (or a union of literal keys if team is a union). K extends PropertyKey restricts keys to what objects can hold. ??= creates each bucket lazily.

Modern runtimes ship Object.groupBy (ES2024) and Map.groupBy, which return a null-prototype object / a Map — worth mentioning, together with why a Map is safer for arbitrary keys like "__proto__".

Reference solution

function groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: (item: T) => K): Record<K, T[]> {
  const groups = {} as Record<K, T[]>;
  for (const item of items) {
    (groups[keyFn(item)] ??= []).push(item);
  }
  return groups;
}

Common mistakes

  • Typing the result as any or {[key: string]: any} and losing inference.
  • Using a plain object with untrusted keys (__proto__) — prefer Map or a null-prototype object.

Follow-up questions

  • Return a Map<K, T[]> instead.
  • Write countBy and indexBy with the same generics.