Array Grouping in JavaScript (2024)

Peter Mbanugo - Aug 5 - - Dev Community

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 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 or lodash.

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

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);
Enter fullscreen mode Exit fullscreen mode

The output for groupByDepartment should be:

{
    "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"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

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:

const groupByDepartment = Object.groupBy(employees, ({department}) => department)
Enter fullscreen mode Exit fullscreen mode

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

{
    "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"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

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:

const managerWithTeammates = Map.groupBy(employees, ({manager}) => manager)
Enter fullscreen mode Exit fullscreen mode

The output for managerWithTeammates should look like:

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"
}]}
Enter fullscreen mode Exit fullscreen mode

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. Thanks for reading 😎

You will find a playground with the code used in this blog post using this link.

If you're interested in similar topics but in video format, subscribe to my YouTube channel: www.youtube.com/@pmbanugo

Originally authored and published by me on Telerik's blog

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .