TypeOfNaN

Beware the Reduce / Spread Operator Combination!

Nick Scialli
August 24, 2019

This is a short post about a popular pattern that has been emerging recently: using the Array reduce method and spread operator (...) together. Let’s look at a quick example where we use them together to swap the keys and values of an object.

const obj = { a: 1, b: 2, c: 3 };
const flipped = Object.entries(obj).reduce(
  (all, [key, val]) => ({
    ...all,
    [val]: key,
  }),
  {}
);
console.log(flipped);
// { '1': 'a', '2': 'b', '3': 'c' }

Pretty cool, right?

The Problem: Using Reduce and Spread Together Can Be Pretty Inefficient for Large Arrays

If you study the example above, you’ll realize that we’re creating a shallow copy of our all object each time we execute the function being passed to the reduce method. This may not be a big deal for small arrays, but can quickly become a performance bottleneck. Let’s create a really large array and test how our reduce/spread combination performs.

const largeArr = new Array(100000).fill(1).map((_, i) => i);
// [0, 1, 2, 3, ..., 99999]

console.time('try 1');
const obj1 = largeArr.reduce(
  (all, el) => ({
    ...all,
    [el]: el,
  }),
  {}
);
console.timeEnd('try 1');
// try 1: 20985.787ms

Yikes, that took 21 seconds! So what are we to do?

Plain Old Loops to the Rescue

In a lot of cases, it turns out that a plain old for loop or forEach method will be sufficient for the task. In the following example, we accomplish the same task as the above code in only ~5 milliseconds.

console.time('try 2');
const obj2 = {};
for (let i = 0; i < largeArr.length; i++) {
  obj2[largeArr[i]] = largeArr[i];
}
console.timeEnd('try 2');
// try 2: 4.620ms

Conclusion

Think hard before using reduce and spread (...) together! You’re probably fine if you’ll be working with small arrays, but can definitely take a performance hit when your arrays get large.

Nick Scialli

Nick Scialli is a senior UI engineer at Microsoft.

© 2024 Nick Scialli