{ ILoveJS }

Group array by key

typescript

A generic TypeScript function that groups array elements by a computed key, returning a Record<string, T[]> where each key maps to an array of matching elements.

arraygroupbyobject

Code

typescript
type GroupedRecord<T> = Record<string, T[]>;

function groupBy<T>(array: T[], keySelector: (item: T) => string): GroupedRecord<T> {
  return array.reduce<GroupedRecord<T>>((accumulator, currentItem) => {
    const key = keySelector(currentItem);
    
    if (!accumulator[key]) {
      accumulator[key] = [];
    }
    
    accumulator[key].push(currentItem);
    
    return accumulator;
  }, {});
}

// Alternative implementation using Object.groupBy (ES2024+)
function groupByModern<T>(array: T[], keySelector: (item: T) => string): GroupedRecord<T> {
  return Object.groupBy(array, keySelector) as GroupedRecord<T>;
}

// Example usage with different data types
interface User {
  id: number;
  name: string;
  department: string;
  role: string;
}

const users: User[] = [
  { id: 1, name: "Alice", department: "Engineering", role: "Developer" },
  { id: 2, name: "Bob", department: "Marketing", role: "Manager" },
  { id: 3, name: "Charlie", department: "Engineering", role: "Developer" },
  { id: 4, name: "Diana", department: "Marketing", role: "Analyst" },
  { id: 5, name: "Eve", department: "Engineering", role: "Manager" }
];

// Group by department
const byDepartment = groupBy(users, (user) => user.department);
console.log("Grouped by department:", byDepartment);

// Group by role
const byRole = groupBy(users, (user) => user.role);
console.log("Grouped by role:", byRole);

// Group by composite key
const byDeptAndRole = groupBy(users, (user) => `${user.department}-${user.role}`);
console.log("Grouped by department and role:", byDeptAndRole);

// Works with primitive arrays too
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const byEvenOdd = groupBy(numbers, (n) => (n % 2 === 0 ? "even" : "odd"));
console.log("Numbers grouped by even/odd:", byEvenOdd);

// Edge case: empty array
const emptyResult = groupBy<User>([], (user) => user.department);
console.log("Empty array result:", emptyResult);

How It Works

The groupBy function leverages TypeScript generics to create a flexible, type-safe utility that works with any array type. The generic parameter T represents the type of elements in the input array, and the keySelector function takes an item of type T and returns a string key. This design allows the compiler to infer types automatically in most cases, providing excellent developer experience with autocomplete and type checking.

The implementation uses Array.prototype.reduce to iterate through the array once, building up the grouped object incrementally. For each item, we compute its key using the selector function, initialize an empty array for that key if it doesn't exist, and then push the current item into the appropriate group. The reduce method is explicitly typed with <GroupedRecord<T>> to ensure TypeScript understands the accumulator's shape throughout the reduction process.

A modern alternative using Object.groupBy (introduced in ES2024) is also provided. This native method does the same thing more concisely, but requires a type assertion because its return type uses Partial<Record<K, T[]>> to account for the possibility that some keys might not be present. The polyfill-style implementation with reduce is more compatible with older environments and gives you more control over edge cases.

The function handles several edge cases gracefully: empty arrays return an empty object, items with the same computed key are grouped together in insertion order, and the key selector can return any string including computed composite keys. One limitation to be aware of is that keys are always strings—if you need symbol or numeric keys, you'd need to modify the return type accordingly.

Use this pattern when you need to categorize data for display (like grouping products by category), aggregate statistics (counting items per group), or transform flat data into hierarchical structures. Avoid it when working with very large datasets where memory is a concern, as it creates a new object containing all elements. For streaming or lazy evaluation scenarios, consider using generators or libraries like Lodash that offer lazy evaluation options.