Should I avoid using object spread in reduce?
Asked Answered
C

3

8

Given the following example:

// option 1
items.reduce((values, item) => ({
     ...values,
     [item.id]: item.name
}), {})


// option 2
items.reduce((values, item) => {
    values[item.id] = item.name;
    return values;
}, {});

Is there a best practice pro or contra using object spread syntax in this case?

Conic answered 30/11, 2019 at 23:36 Comment(2)
creating a new object with all properties every time sounds more resource consuming (and likely isn't optimized away by any jitter). However, this only matters, if this operation is a bottleneck.Hobnailed
A better option would be to use more appropriate language features, e.g. new Map(items.map(x => [x.id, x.name]))Tuneless
B
7

Option 2 is clearly preferable for performance reasons:

  • Option 1 runs in O(n²) time, since the spread syntax copies O(n) properties on each iteration. Option 2 runs in O(n) time.
  • Option 1 creates O(n²) garbage, since it creates a garbage object of size O(n) on each iteration. Option 2 creates no garbage.

That said, in most cases you should just write this with a plain old for loop:

let result = {};
for(let item of items) {
    result[item.id] = item.name;
}

It is not bad to use for loops, and the code is even more readable than the two options in the question. Option 2 may seem like it is more in the functional programming style, but if you are using mutation to achieve your desired result then you are not really doing functional programming.

See this article for a more in-depth discussion of why Option 1 is an antipattern.

Beard answered 30/11, 2021 at 12:43 Comment(2)
"if you are using mutation to achieve your desired result" - can you explain this more? do you mean the mutation of items?Illiberal
@Illiberal The mutation of values in the reduce function. The stylistic difference between Option 1 and Option 2 is that Option 1 doesn't mutate the accumulator, it creates a new accumulator instead, which is in line with the functional style. Option 2 mutates the accumulator so it is not really in the functional style. There is more discussion in the linked article, under the heading "Functional purity".Beard
R
3

In the first code, you're creating a new object for every iteration of .reduce. In certain engines, this may be slightly less efficient than your second code, which only creates a single object. (That said, efficiency rarely matters much; code clarity is much more important in most situations).

But, for this situation, there's an even more suitable method to use when creating an object from an array, which avoids the slightly clunky syntax of reduce:

const output = Object.fromEntries(
  items.map(item => [item.id, item])
);

const items = [
  { id: 5, val: 5 },
  { id: 10, val: 10 },
  { id: 15, val: 15 },
];
const output = Object.fromEntries(
  items.map(item => [item.id, item])
);
console.log(output);

That said, keep in mind that Object.fromEntries is a relatively new feature, so if this is meant for a public-facing website, make sure to include a polyfill.

Rodriquez answered 30/11, 2019 at 23:41 Comment(0)
H
2

...values will create a shallow copy of your array each time, which may prove costly if the array is large. Setting the property on the accumulator, on the other hand, is more efficient. That being said, you could determine that your array is certain to be small enough that you'd prefer the terseness of the spread syntax.

Herrle answered 30/11, 2019 at 23:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.