Back to Blog
JavaScriptObject.groupByArraysES2024

JavaScript Object.groupBy Method Guide

Master the JavaScript Object.groupBy method for grouping array elements into objects by key.

B
Bootspring Team
Engineering
November 23, 2019
7 min read

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 strings

Aggregating 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.

Share this article

Help spread the word about Bootspring