JavaScript ({…Spread}) and reduce(): Avoiding a common anti-pattern

JavaScript ({…Spread}) and reduce(): Avoiding a common anti-pattern

Table of Contents

  • Introduction

  • The anti-pattern

  • The solution

  • Performance comparison (following benchmark)

  • Conclusion

Introduction:

Spreading is a powerful feature in JavaScript that allows us to expand an iterable object (such as an array or string) into a list of individual elements. This can be very useful for simplifying our code and making it more readable. However, there is a common anti-pattern that can occur when using spreading in the context of the reduce() method.

The anti-pattern:

The following code snippet shows the anti-pattern:

const people = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  { id: 3, name: "Carol" },
];

const lookup = people.reduce((lookup, person) => ({
  ...lookup,
  [person.id]: person,
}), {});

console.log(lookup);

This code will create a new object for each iteration of the reduce() method. This can be very inefficient, especially if the array is large.

The solution:

The following code snippet shows the correct approach:

const people = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  { id: 3, name: "Carol" },
];

const lookup = people.reduce((lookup, person) => {
  lookup[person.id] = person;
  return lookup;
}, {});

console.log(lookup);

This code is more efficient because it reuses the same object for each iteration of the reduce() method. The only difference is that we are spreading the id property from the person object directly, rather than creating a new object and then spreading from it.

Performance comparison:

The following benchmark shows the difference in performance between the two approaches:

const people = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Person ${i}` }));

const benchmark = (fn) => {
  const start = performance.now();
  fn();
  const end = performance.now();
  return end - start;
};

const wrongWay = () => {
  const lookup = people.reduce((lookup, person) => ({
    ...lookup,
    [person.id]: person,
  }), {});
};

const correctWay = () => {
  const lookup = people.reduce((lookup, person) => {
    lookup[person.id] = person;
    return lookup;
  }, {});
};

const wrongWayTime = benchmark(wrongWay);
const correctWayTime = benchmark(correctWay);

console.log(`Wrong way time: ${wrongWayTime}ms`);
console.log(`Correct way time: ${correctWayTime}ms`);

Output:

 Wrong way time: 31.899999999441206ms
 Correct way time: 0.5ms

As you can see, the correct approach is significantly faster than the wrong approach.

Conclusion:

When using spreading in the context of the reduce() method, it is important to be aware of this anti-pattern. By reusing the same object for each iteration of the reduce() method, you can significantly improve the performance of your code.

Let's connect: