groupByMulti
Groups items into multiple buckets based on multiple keys per item. Each item can appear in multiple groups if the key extractor returns an array.
1/**
2 * Groups items into multiple buckets based on multiple keys per item.
3 * Each item can appear in multiple groups if the key extractor returns an array.
4 *
5 * @param arr - The array to group.
6 * @param fn - A function that returns one or more keys for each item.
7 * @returns An object where each key maps to an array of grouped items.
8 */
9export function groupByMulti<T, K extends string | number>(
10 arr: T[],
11 fn: (item: T) => K | K[]
12): Record<K, T[]> {
13 return arr.reduce((acc, item) => {
14 const keys = fn(item);
15 const keyList = Array.isArray(keys) ? keys : [keys];
16 for (const key of keyList) {
17 if (!acc[key]) acc[key] = [];
18 acc[key].push(item);
19 }
20 return acc;
21 }, {} as Record<K, T[]>);
22}Multi-key grouping support
Unlike
groupBy, this utility allows each item to be placed into multiple groups by returning an array of keys. This is ideal when items logically belong to several categories.Efficient and concise
Uses a single
reduceloop and minimal branching, ensuring performance remains linear (O(n * m), wheremis the average number of keys per item).Flexible key extraction
Accepts either a single key or multiple keys per item via the extractor function, offering greater versatility in grouping logic.
Non-mutating and type-safe
Produces a new
Record<K, T[]>while preserving original data, with full TypeScript type support.
Tests | Examples
1test('groupByMulti - group by multiple tags', () => {
2 const posts = [
3 { title: 'Post 1', tags: ['react', 'javascript'] },
4 { title: 'Post 2', tags: ['css', 'design'] },
5 { title: 'Post 3', tags: ['javascript'] },
6 ];
7
8 expect(groupByMulti(posts, post => post.tags)).toEqual({
9 react: [{ title: 'Post 1', tags: ['react', 'javascript'] }],
10 javascript: [
11 { title: 'Post 1', tags: ['react', 'javascript'] },
12 { title: 'Post 3', tags: ['javascript'] },
13 ],
14 css: [{ title: 'Post 2', tags: ['css', 'design'] }],
15 design: [{ title: 'Post 2', tags: ['css', 'design'] }],
16 });
17});
18
19test('groupByMulti - single key fallback', () => {
20 const data = ['apple', 'banana', 'cherry'];
21 expect(groupByMulti(data, word => word[0])).toEqual({
22 a: ['apple'],
23 b: ['banana'],
24 c: ['cherry'],
25 });
26});Common Use Cases
Tag-based categorization
Group blog posts, files, or products under multiple tags or labels.
Access control / role mapping
Group users or permissions by multiple roles or responsibilities.
Search indexing
Pre-process data for fast lookup across multiple indexed fields or terms.
Reverse-mapping datasets
For example, mapping songs to genres when a song belongs to multiple genres.