frontend
Array GroupBy Polyfill in JavaScript
January 24, 2026
Array GroupBy Polyfill in JavaScript
Overview
The groupBy method groups array elements based on a provided function. It returns an object where keys are the results of the grouping function and values are arrays of elements that produced that key. This is similar to SQL's GROUP BY clause and is useful for organizing and categorizing data.
Basic Implementation
/**
* @description Group the array by the given function
* @param {Function} fn
* @returns {Object}
*/
Array.prototype.groupBy = function (fn) {
const result = {};
for (const item of this) {
const key = fn(item);
if (!result[key]) {
result[key] = [];
}
result[key].push(item);
}
return result;
};
Examples
Group by String Conversion
console.log([1, 2, 3, 4, 5, 6].groupBy(String));
// {
// "1": [1],
// "2": [2],
// "3": [3],
// "4": [4],
// "5": [5],
// "6": [6]
// }
Group by Modulo
console.log([1, 2, 3, 4, 5, 6].groupBy((n) => n % 2));
// {
// "0": [2, 4, 6],
// "1": [1, 3, 5]
// }
Advanced Implementation
Using Reduce
Array.prototype.groupBy = function (fn) {
return this.reduce((result, item) => {
const key = fn(item);
result[key] = result[key] || [];
result[key].push(item);
return result;
}, {});
};
With Map for Better Performance
Array.prototype.groupBy = function (fn) {
const map = new Map();
for (const item of this) {
const key = fn(item);
if (!map.has(key)) {
map.set(key, []);
}
map.get(key).push(item);
}
return Object.fromEntries(map);
};
Use Cases
1. Group Objects by Property
const users = [
{ name: 'John', age: 25, city: 'New York' },
{ name: 'Jane', age: 30, city: 'London' },
{ name: 'Bob', age: 25, city: 'New York' },
{ name: 'Alice', age: 30, city: 'Paris' }
];
// Group by age
const groupedByAge = users.groupBy(user => user.age);
// {
// 25: [{ name: 'John', age: 25, city: 'New York' }, { name: 'Bob', age: 25, city: 'New York' }],
// 30: [{ name: 'Jane', age: 30, city: 'London' }, { name: 'Alice', age: 30, city: 'Paris' }]
// }
// Group by city
const groupedByCity = users.groupBy(user => user.city);
2. Group by Multiple Properties
Array.prototype.groupByMultiple = function (...fns) {
return this.reduce((result, item) => {
const key = fns.map(fn => fn(item)).join('|');
result[key] = result[key] || [];
result[key].push(item);
return result;
}, {});
};
// Usage
const grouped = users.groupByMultiple(
user => user.age,
user => user.city
);
3. Group by Date
const events = [
{ name: 'Event 1', date: new Date('2024-01-15') },
{ name: 'Event 2', date: new Date('2024-01-15') },
{ name: 'Event 3', date: new Date('2024-01-16') }
];
const groupedByDate = events.groupBy(event =>
event.date.toISOString().split('T')[0]
);
4. Group by Category
const products = [
{ name: 'Laptop', category: 'Electronics', price: 1000 },
{ name: 'Phone', category: 'Electronics', price: 800 },
{ name: 'Book', category: 'Education', price: 20 },
{ name: 'Pen', category: 'Education', price: 2 }
];
const groupedByCategory = products.groupBy(product => product.category);
// {
// Electronics: [{ name: 'Laptop', ... }, { name: 'Phone', ... }],
// Education: [{ name: 'Book', ... }, { name: 'Pen', ... }]
// }
5. Group by Range
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const groupedByRange = numbers.groupBy(n => {
if (n <= 3) return 'low';
if (n <= 6) return 'medium';
return 'high';
});
// {
// low: [1, 2, 3],
// medium: [4, 5, 6],
// high: [7, 8, 9, 10]
// }
Advanced Features
GroupBy with Transformation
Array.prototype.groupByWithTransform = function (fn, transform) {
return this.reduce((result, item) => {
const key = fn(item);
if (!result[key]) {
result[key] = [];
}
result[key].push(transform ? transform(item) : item);
return result;
}, {});
};
// Usage
const grouped = users.groupByWithTransform(
user => user.age,
user => user.name // Only store names
);
GroupBy with Aggregation
Array.prototype.groupByWithAggregate = function (fn, aggregateFn) {
return this.reduce((result, item) => {
const key = fn(item);
if (!result[key]) {
result[key] = [];
}
result[key].push(item);
return result;
}, {}).mapValues((group, key) => aggregateFn(group, key));
};
// Helper to map object values
Object.prototype.mapValues = function (fn) {
return Object.fromEntries(
Object.entries(this).map(([key, value]) => [key, fn(value, key)])
);
};
// Usage: Group and sum prices
const products = [
{ category: 'Electronics', price: 100 },
{ category: 'Electronics', price: 200 },
{ category: 'Books', price: 20 }
];
const grouped = products.groupByWithAggregate(
p => p.category,
(group) => group.reduce((sum, p) => sum + p.price, 0)
);
GroupBy with Count
Array.prototype.groupByCount = function (fn) {
return this.reduce((result, item) => {
const key = fn(item);
result[key] = (result[key] || 0) + 1;
return result;
}, {});
};
// Usage
const countByAge = users.groupByCount(user => user.age);
// { 25: 2, 30: 2 }
Native Implementation (ES2024)
JavaScript now has a native groupBy method (ES2024):
// Native implementation (if available)
const grouped = [1, 2, 3, 4, 5, 6].groupBy(n => n % 2);
Performance Considerations
Using Map (Faster for Large Arrays)
Array.prototype.groupByFast = function (fn) {
const map = new Map();
for (let i = 0; i < this.length; i++) {
const key = fn(this[i]);
const group = map.get(key);
if (group) {
group.push(this[i]);
} else {
map.set(key, [this[i]]);
}
}
return Object.fromEntries(map);
};
Real-World Examples
Example 1: Group Orders by Status
const orders = [
{ id: 1, status: 'pending', amount: 100 },
{ id: 2, status: 'completed', amount: 200 },
{ id: 3, status: 'pending', amount: 150 },
{ id: 4, status: 'cancelled', amount: 50 }
];
const ordersByStatus = orders.groupBy(order => order.status);
// {
// pending: [{ id: 1, ... }, { id: 3, ... }],
// completed: [{ id: 2, ... }],
// cancelled: [{ id: 4, ... }]
// }
Example 2: Group Students by Grade
const students = [
{ name: 'Alice', grade: 'A', score: 95 },
{ name: 'Bob', grade: 'B', score: 85 },
{ name: 'Charlie', grade: 'A', score: 92 },
{ name: 'David', grade: 'B', score: 88 }
];
const studentsByGrade = students.groupBy(student => student.grade);
Example 3: Group Transactions by Month
const transactions = [
{ amount: 100, date: new Date('2024-01-15') },
{ amount: 200, date: new Date('2024-01-20') },
{ amount: 150, date: new Date('2024-02-10') }
];
const transactionsByMonth = transactions.groupBy(transaction => {
return transaction.date.toLocaleString('default', { month: 'long', year: 'numeric' });
});
Best Practices
- Use Meaningful Keys: Ensure grouping function returns meaningful keys
- Handle Null/Undefined: Consider how to handle null/undefined values
- Performance: Use Map for large arrays
- Type Safety: Ensure consistent key types
- Immutable: Consider creating new arrays instead of modifying original
Comparison with Other Methods
vs filter()
- filter(): Returns single array matching condition
- groupBy(): Returns object with multiple groups
vs reduce()
- reduce(): More flexible but more verbose
- groupBy(): Specialized for grouping operations
vs Map/Set
- Map/Set: More efficient for large datasets
- groupBy(): More convenient for simple grouping