# Array Grouping in JavaScript

Array grouping is a task you likely have implemented in JavaScript. If you use SQL, it's similar to doing a `GROUP BY`. Given a dataset, we can compose a higher-level dataset by putting like data in a group and identifying each group by a given identifier.

I'll dive into the new *Array grouping* functions released this year (2024), namely, `Object.groupBy` and `Map.groupBy`.

TypeScript support for these APIs is available from [TypeScript 5.4](https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/#objectgroupby-and-mapgroupby) for my TypeScript fans. However, you'd have to configure your `tsconfig.json` to target `ESNext`. When they're available in ES2024, you can set the target to `ES2024` or higher.

## Array Grouping pre-2024

Array grouping in JavaScript is not a new concept, we have implemented it in various. It could be done with a `for` or `foreach` loop, `Array.prototype.reduce`, or *groupBy* function in [underscore.js](https://underscorejs.org/#groupBy) or [lodash](https://lodash.com/docs/4.17.15#groupBy).

Given a list of employees, here's how we would group the data using `reduce()`:

```typescript
interface Employee {
  name: string;
  department: string;
  age: number;
  manager?: Employee;
  joined: Date
}

const ceo = {
    name: "John Doe",
    department: "engineering",
    age: 47,
    joined: new Date("10-04-2020")
}

const cfo = {
    name: "Anna Maria",
    department: "finance",
    age: 45,
    joined: new Date("10-05-2020")
  }

 const employees: Employee[] = [
  ceo,
  {
    name: "Pop Jones Jr.",
    department: "finance",
    age: 30,
    manager: cfo,
    joined: new Date("10-04-2021")
  },
  {
    name: "Sarah Clara",
    department: "engineering",
    age: 32,
    manager: ceo,
    joined: new Date("10-05-2021")
  },
  cfo,
  {
    name: "Funmi Kola",
    department: "engineering",
    age: 20,
    manager: ceo,
    joined: new Date("10-05-2022")
  },
  {
    name: "Julius Maria",
    department: "sales",
    age: 27,
    manager: cfo,
    joined: new Date("10-05-2022")
  }
 ]

const groupByDepartment = employees.reduce<Record<string, Employee[]>>((acc, employee) => {
  const department = employee.department;
  if (acc[department] === undefined) {
    acc[department] = [];
  }

  acc[department].push(employee);
  return acc;
}, {});

console.log(groupByDepartment);
```

The output for `groupByDepartment` should be:

```javascript
{
    "engineering": [
        {
            "name": "John Doe",
            "department": "engineering",
            "age": 47,
            "joined": "2020-10-03T22:00:00.000Z"
        },
        {
            "name": "Sarah Clara",
            "department": "engineering",
            "age": 32,
            "manager": {
                "name": "John Doe",
                "department": "engineering",
                "age": 47,
                "joined": "2020-10-03T22:00:00.000Z"
            },
            "joined": "2021-10-04T22:00:00.000Z"
        },
        {
            "name": "Funmi Kola",
            "department": "engineering",
            "age": 20,
            "manager": {
                "name": "John Doe",
                "department": "engineering",
                "age": 47,
                "joined": "2020-10-03T22:00:00.000Z"
            },
            "joined": "2022-10-04T22:00:00.000Z"
        }
    ],
    "finance": [
        {
            "name": "Pop Jones Jr.",
            "department": "finance",
            "age": 30,
            "manager": {
                "name": "Anna Maria",
                "department": "finance",
                "age": 45,
                "joined": "2020-10-04T22:00:00.000Z"
            },
            "joined": "2021-10-03T22:00:00.000Z"
        },
        {
            "name": "Anna Maria",
            "department": "finance",
            "age": 45,
            "joined": "2020-10-04T22:00:00.000Z"
        }
    ],
    "sales": [
        {
            "name": "Julius Maria",
            "department": "sales",
            "age": 27,
            "manager": {
                "name": "Anna Maria",
                "department": "finance",
                "age": 45,
                "joined": "2020-10-04T22:00:00.000Z"
            },
            "joined": "2022-10-04T22:00:00.000Z"
        }
    ]
}
```

## Grouping With Object.groupBy

The `Object.groupBy` function is used to group by a given string value. That means: given an iterable, group its elements according to the value returned by the provided callback function. The value returned from the callback should be something that can be coerced into a string or symbol.

`Object.groupBy` returns a null-prototype object which has separate properties for each group, containing arrays with the elements in the group. Using null-prototype object allows for ergonomic destructuring and prevents accidental collisions with the global Object properties.

Using the same dataset, we can group the employees by their department using `Object.groupBy`:

```typescript
const groupByDepartment = Object.groupBy(employees, ({department}) => department)
```

This gives us the same result as the `reduce` method but with less code. The output for `groupByDepartment` should be:

```javascript
{
    "engineering": [
        {
            "name": "John Doe",
            "department": "engineering",
            "age": 47,
            "joined": "2020-10-03T22:00:00.000Z"
        },
        {
            "name": "Sarah Clara",
            "department": "engineering",
            "age": 32,
            "manager": {
                "name": "John Doe",
                "department": "engineering",
                "age": 47,
                "joined": "2020-10-03T22:00:00.000Z"
            },
            "joined": "2021-10-04T22:00:00.000Z"
        },
        {
            "name": "Funmi Kola",
            "department": "engineering",
            "age": 20,
            "manager": {
                "name": "John Doe",
                "department": "engineering",
                "age": 47,
                "joined": "2020-10-03T22:00:00.000Z"
            },
            "joined": "2022-10-04T22:00:00.000Z"
        }
    ],
    "finance": [
        {
            "name": "Pop Jones Jr.",
            "department": "finance",
            "age": 30,
            "manager": {
                "name": "Anna Maria",
                "department": "finance",
                "age": 45,
                "joined": "2020-10-04T22:00:00.000Z"
            },
            "joined": "2021-10-03T22:00:00.000Z"
        },
        {
            "name": "Anna Maria",
            "department": "finance",
            "age": 45,
            "joined": "2020-10-04T22:00:00.000Z"
        }
    ],
    "sales": [
        {
            "name": "Julius Maria",
            "department": "sales",
            "age": 27,
            "manager": {
                "name": "Anna Maria",
                "department": "finance",
                "age": 45,
                "joined": "2020-10-04T22:00:00.000Z"
            },
            "joined": "2022-10-04T22:00:00.000Z"
        }
    ]
}
```

## Grouping With Map.groupBy

`Map.groupBy` is similar to `Object.groupBy`, except that it returns a Map and the given iterable can be grouped using any arbitrary value. In this case, the dataset can be grouped using an object.

Looking back to the dataset, we defined a `manager` property for some employees and assigned it the `ceo` or `cfo` object. We can group the employees by their `manager`, which would look like this:

```typescript
const managerWithTeammates = Map.groupBy(employees, ({manager}) => manager)
```

The output for `managerWithTeammates` should look like:

```javascript
Map (3) 
{undefined => [{
  "name": "John Doe",
  "department": "engineering",
  "age": 47,
  "joined": "2020-10-03T22:00:00.000Z"
}, {
  "name": "Anna Maria",
  "department": "finance",
  "age": 45,
  "joined": "2020-10-04T22:00:00.000Z"
}], 
{
  "name": "Anna Maria",
  "department": "finance",
  "age": 45,
  "joined": "2020-10-04T22:00:00.000Z"
} => [{
  "name": "Pop Jones Jr.",
  "department": "finance",
  "age": 30,
  "manager": {
    "name": "Anna Maria",
    "department": "finance",
    "age": 45,
    "joined": "2020-10-04T22:00:00.000Z"
  },
  "joined": "2021-10-03T22:00:00.000Z"
}, {
  "name": "Julius Maria",
  "department": "sales",
  "age": 27,
  "manager": {
    "name": "Anna Maria",
    "department": "finance",
    "age": 45,
    "joined": "2020-10-04T22:00:00.000Z"
  },
  "joined": "2022-10-04T22:00:00.000Z"
}], 
{
  "name": "John Doe",
  "department": "engineering",
  "age": 47,
  "joined": "2020-10-03T22:00:00.000Z"
} => [{
  "name": "Sarah Clara",
  "department": "engineering",
  "age": 32,
  "manager": {
    "name": "John Doe",
    "department": "engineering",
    "age": 47,
    "joined": "2020-10-03T22:00:00.000Z"
  },
  "joined": "2021-10-04T22:00:00.000Z"
}, {
  "name": "Funmi Kola",
  "department": "engineering",
  "age": 20,
  "manager": {
    "name": "John Doe",
    "department": "engineering",
    "age": 47,
    "joined": "2020-10-03T22:00:00.000Z"
  },
  "joined": "2022-10-04T22:00:00.000Z"
}]}
```

With this example, we have 3 groups:

1. The CEO and CFO who doesn't have a manager, with `undefined` as the Map object key.
    
2. The CFO, Anna Maria, who has two employees under her. This uses the `cfo` object as the Map object key.
    
3. The CEO, John Doe, who has two employees under him. This uses the `ceo` object as the Map object key.
    

## Conclusion

Isn't it amazing the new APIs that are coming to JavaScript? The `Object.groupBy` and `Map.groupBy` functions make it easier to group data in an array or iterable, and they are more ergonomic than the traditional `reduce` method. They also reduce your bundle size by not requiring an external library like `lodash` or `underscore.js`.

To recap, use the `Map.groupBy` function primarily when grouping elements associated with any arbitrary object, particularly when that object might change over time. If the object is invariant, you might instead represent it using a string, and group elements with `Object.groupBy()`. If using TypeScript, set the target to `ESNext` (or `ES2024` when it's available in the future).

If you have any questions or suggestions, feel free to leave a comment. You can also reach me on [Twitter](https://twitter.com/p_mbanugo). Thanks for reading 😎

> You will find a playground with the code used in this blog post using this [link](https://dub.sh/RYpbpvh).
> 
> If you're interested in similar topics but in video format, subscribe to my [YouTube channel](https://www.youtube.com/@pmbanugo): [www.youtube.com/@pmbanugo](https://www.youtube.com/@pmbanugo)
> 
> Originally authored and published by me on [Telerik's blog](https://www.telerik.com/blogs/array-grouping-javascript)
