Group items by types: replace nested for loops with reduce I have a working code that groups items by their type values. The types can be one or multiple, however they are always returned as an array. 

To illustrate: if an item has types with values type1 and type2, it should be included in both type1 and type2 properties of the resulting grouped object.

Although the code works, I was wondering if it would be possible to rewrite it without using nested for loops, to make it more aligned with the principles of functional programming, ideally using reduce. Since the data sets are not too large, performance is a secondary consideration, but should be taken into account if possible.

The code is as follows. getTypes is added primarily to show how the types are extracted and ideally shouldn't be changed.



const items = [{
    name: 'First item',
    types: [{
        value: 'type1',
      },
      {
        value: 'type2',
      },
    ],
  },
  {
    name: 'Second item',
    types: [{
        value: 'type1',
      },
      {
        value: 'type3',
      },
    ],
  },
  {
    name: 'Third item',
    types: [{
        value: 'type1',
      },
      {
        value: 'type2',
      },
    ],
  },
];

const getTypes = item =&gt; item.types.map(type =&gt; type.value);

const groupedItems = {};

for (const item of items) {
  for (const type of getTypes(item)) {
    groupedItems[type] = [...(groupedItems[type] || []), item];
  }
}

console.log(JSON.stringify(groupedItems, null, 4));




Code output:

{
    "type1": [
        {
            "name": "First item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type2"
                }
            ]
        },
        {
            "name": "Second item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type3"
                }
            ]
        },
        {
            "name": "Third item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type2"
                }
            ]
        }
    ],
    "type2": [
        {
            "name": "First item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type2"
                }
            ]
        },
        {
            "name": "Third item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type2"
                }
            ]
        }
    ],
    "type3": [
        {
            "name": "Second item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type3"
                }
            ]
        }
    ]
}
Poor memory usage
Javascript has managed memory. That means it does all the hard work of allocating and releasing memory, which is great (managing memory manually is a real pain).
But this useful feature comes with a down sides...

Managed memory environments are inherently slow,
Managed memory environments encourage very poor memory usage patterns

Looking at your memory and CPU usage.
In your code you create a total of 9 arrays, keeping only 3 of them. (using the data example you provided)
Each time you add an item to a group you create a new array, that must be iterated over when you copy the existing group. groupedItems[type] = [...(groupedItems[type] || []), item]
There are only 6 type values yet you iterate over them 12 times. Once each with item.types.map(type =&gt; type.value); and then again with for (const type of getTypes(item))
For the 6 items you add to the 3 groups your code needed 16 iterations.
Less overhead
The example avoids copying the item.types array for each item and assigns a new array to new groups. If a group has been defined the item added is pushed rather than copy the whole array.
The resulting code only needs 6 iterations executing in half the time and using several times less memory
function groupTypes(items) {
    const groups = {};
    for (const item of items) {
        for (const {value: type} of item.types) {
            groups[type] ? groups[type].push(item) : groups[type] = [item];
        }
    }
    return groups;
}

Or slightly quicker by avoiding the copy of the type.value string to to the variable type
function groupTypes(items) {
    const grps = {};
    for (const item of items) {
        for (const type of item.types) {
            grps[type.value] ? grps[type.value].push(item) : grps[type.value] = [item];
        }
    }
    return grps ;
}