The Object.groupBy method groups array elements into an object based on a callback function's return value. Here's how to use it.
Basic Usage#
1const inventory = [
2 { name: 'asparagus', type: 'vegetables', quantity: 5 },
3 { name: 'bananas', type: 'fruit', quantity: 0 },
4 { name: 'goat', type: 'meat', quantity: 23 },
5 { name: 'cherries', type: 'fruit', quantity: 5 },
6 { name: 'fish', type: 'meat', quantity: 22 },
7];
8
9// Group by type
10const grouped = Object.groupBy(inventory, ({ type }) => type);
11
12// Result:
13// {
14// vegetables: [{ name: 'asparagus', type: 'vegetables', quantity: 5 }],
15// fruit: [
16// { name: 'bananas', type: 'fruit', quantity: 0 },
17// { name: 'cherries', type: 'fruit', quantity: 5 }
18// ],
19// meat: [
20// { name: 'goat', type: 'meat', quantity: 23 },
21// { name: 'fish', type: 'meat', quantity: 22 }
22// ]
23// }Group by Property#
1const users = [
2 { name: 'Alice', role: 'admin' },
3 { name: 'Bob', role: 'user' },
4 { name: 'Charlie', role: 'admin' },
5 { name: 'Diana', role: 'moderator' },
6 { name: 'Eve', role: 'user' },
7];
8
9// Group by role
10const byRole = Object.groupBy(users, (user) => user.role);
11
12// {
13// admin: [{ name: 'Alice', ... }, { name: 'Charlie', ... }],
14// user: [{ name: 'Bob', ... }, { name: 'Eve', ... }],
15// moderator: [{ name: 'Diana', ... }]
16// }
17
18// Access specific group
19console.log(byRole.admin);
20// [{ name: 'Alice', role: 'admin' }, { name: 'Charlie', role: 'admin' }]Group by Computed Value#
1const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
2
3// Group by odd/even
4const byParity = Object.groupBy(numbers, (n) => n % 2 === 0 ? 'even' : 'odd');
5
6// { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8, 10] }
7
8// Group by range
9const byRange = Object.groupBy(numbers, (n) => {
10 if (n <= 3) return 'low';
11 if (n <= 7) return 'medium';
12 return 'high';
13});
14
15// { low: [1, 2, 3], medium: [4, 5, 6, 7], high: [8, 9, 10] }
16
17// Group by first letter
18const words = ['apple', 'apricot', 'banana', 'blueberry', 'cherry'];
19const byLetter = Object.groupBy(words, (word) => word[0]);
20
21// { a: ['apple', 'apricot'], b: ['banana', 'blueberry'], c: ['cherry'] }Group by Date#
1const events = [
2 { title: 'Meeting', date: '2024-01-15' },
3 { title: 'Workshop', date: '2024-01-15' },
4 { title: 'Conference', date: '2024-01-16' },
5 { title: 'Webinar', date: '2024-01-17' },
6 { title: 'Training', date: '2024-01-15' },
7];
8
9// Group by date
10const byDate = Object.groupBy(events, (event) => event.date);
11
12// Group by month
13const byMonth = Object.groupBy(events, (event) => {
14 const date = new Date(event.date);
15 return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
16});
17
18// Group by day of week
19const byDayOfWeek = Object.groupBy(events, (event) => {
20 const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
21 return days[new Date(event.date).getDay()];
22});Map.groupBy#
1// Group using Map (preserves key type)
2const products = [
3 { name: 'Laptop', category: { id: 1, name: 'Electronics' } },
4 { name: 'Phone', category: { id: 1, name: 'Electronics' } },
5 { name: 'Shirt', category: { id: 2, name: 'Clothing' } },
6 { name: 'Pants', category: { id: 2, name: 'Clothing' } },
7];
8
9// Use Map.groupBy for object keys
10const byCategory = Map.groupBy(products, (p) => p.category);
11
12// Map with category objects as keys
13for (const [category, items] of byCategory) {
14 console.log(`${category.name}:`, items.map((i) => i.name));
15}
16
17// Object.groupBy converts keys to strings
18const asObject = Object.groupBy(products, (p) => p.category.id);
19// { '1': [...], '2': [...] } - keys are stringsAggregating After Grouping#
1const sales = [
2 { product: 'Widget', region: 'North', amount: 100 },
3 { product: 'Gadget', region: 'South', amount: 200 },
4 { product: 'Widget', region: 'North', amount: 150 },
5 { product: 'Gadget', region: 'North', amount: 75 },
6 { product: 'Widget', region: 'South', amount: 300 },
7];
8
9// Group by product
10const byProduct = Object.groupBy(sales, (s) => s.product);
11
12// Calculate totals per group
13const totals = Object.fromEntries(
14 Object.entries(byProduct).map(([product, items]) => [
15 product,
16 {
17 count: items.length,
18 total: items.reduce((sum, i) => sum + i.amount, 0),
19 average: items.reduce((sum, i) => sum + i.amount, 0) / items.length,
20 },
21 ])
22);
23
24// { Widget: { count: 3, total: 550, average: 183.33 }, ... }
25
26// Group and count
27function groupAndCount(arr, keyFn) {
28 const grouped = Object.groupBy(arr, keyFn);
29 return Object.fromEntries(
30 Object.entries(grouped).map(([key, items]) => [key, items.length])
31 );
32}
33
34const countByRegion = groupAndCount(sales, (s) => s.region);
35// { North: 3, South: 2 }Nested Grouping#
1const data = [
2 { category: 'A', subcategory: 'X', value: 1 },
3 { category: 'A', subcategory: 'Y', value: 2 },
4 { category: 'B', subcategory: 'X', value: 3 },
5 { category: 'A', subcategory: 'X', value: 4 },
6 { category: 'B', subcategory: 'Y', value: 5 },
7];
8
9// Two-level grouping
10function groupByTwo(arr, keyFn1, keyFn2) {
11 const firstLevel = Object.groupBy(arr, keyFn1);
12
13 return Object.fromEntries(
14 Object.entries(firstLevel).map(([key, items]) => [
15 key,
16 Object.groupBy(items, keyFn2),
17 ])
18 );
19}
20
21const nested = groupByTwo(
22 data,
23 (d) => d.category,
24 (d) => d.subcategory
25);
26
27// {
28// A: { X: [...], Y: [...] },
29// B: { X: [...], Y: [...] }
30// }Multiple Keys#
1const orders = [
2 { status: 'pending', priority: 'high', id: 1 },
3 { status: 'completed', priority: 'low', id: 2 },
4 { status: 'pending', priority: 'low', id: 3 },
5 { status: 'pending', priority: 'high', id: 4 },
6];
7
8// Composite key
9const byStatusAndPriority = Object.groupBy(
10 orders,
11 (o) => `${o.status}:${o.priority}`
12);
13
14// { 'pending:high': [...], 'completed:low': [...], 'pending:low': [...] }
15
16// Parse composite key back
17function parseGroups(grouped) {
18 return Object.fromEntries(
19 Object.entries(grouped).map(([key, items]) => {
20 const [status, priority] = key.split(':');
21 return [key, { status, priority, items }];
22 })
23 );
24}Polyfill#
1// For older environments
2if (!Object.groupBy) {
3 Object.groupBy = function (arr, callback) {
4 return arr.reduce((acc, item, index) => {
5 const key = callback(item, index);
6 (acc[key] ??= []).push(item);
7 return acc;
8 }, {});
9 };
10}
11
12if (!Map.groupBy) {
13 Map.groupBy = function (arr, callback) {
14 const map = new Map();
15 arr.forEach((item, index) => {
16 const key = callback(item, index);
17 if (!map.has(key)) {
18 map.set(key, []);
19 }
20 map.get(key).push(item);
21 });
22 return map;
23 };
24}Practical Examples#
1// Group logs by level
2const logs = [
3 { level: 'error', message: 'Failed to connect' },
4 { level: 'info', message: 'Server started' },
5 { level: 'warn', message: 'Deprecated API' },
6 { level: 'error', message: 'Database timeout' },
7 { level: 'info', message: 'Request received' },
8];
9
10const logsByLevel = Object.groupBy(logs, (log) => log.level);
11
12// Group tasks by assignee
13const tasks = [
14 { title: 'Fix bug', assignee: 'Alice' },
15 { title: 'Write tests', assignee: 'Bob' },
16 { title: 'Review PR', assignee: 'Alice' },
17 { title: 'Deploy', assignee: null },
18];
19
20const tasksByAssignee = Object.groupBy(
21 tasks,
22 (t) => t.assignee ?? 'Unassigned'
23);
24
25// Group by boolean condition
26const items = [
27 { name: 'Item 1', active: true },
28 { name: 'Item 2', active: false },
29 { name: 'Item 3', active: true },
30];
31
32const { true: active, false: inactive } = Object.groupBy(
33 items,
34 (i) => i.active
35);Best Practices#
Usage:
✓ Use for categorization
✓ Use callback for dynamic keys
✓ Use Map.groupBy for object keys
✓ Chain with aggregation
Benefits:
✓ Cleaner than reduce
✓ Returns plain object
✓ No mutation of source
✓ Works with any iterable
Patterns:
✓ Group and count
✓ Group and sum
✓ Nested grouping
✓ Composite keys
Avoid:
✗ Modifying source array
✗ Complex key computation
✗ Assuming key order
✗ Forgetting null/undefined keys
Conclusion#
Object.groupBy simplifies grouping array elements by a key function. Use it for categorization, aggregation prep, and organizing data. For object keys that need reference equality, use Map.groupBy instead. Both methods return new collections without modifying the source array.