Reduce is a good illustration of the principle of least power[1]: it's powerful, flexible, general and low-level and can technically achieve any combination of summation, map, filter, find/includes, some/any/every, etc. But reduce is misused if it's reimplementing patterns available in higher-level form.
In cases when reduce is required because (for example) JS doesn't have a sum function, it should be kept simple. `arr.reduce((acc, el) => el + acc, 0)` is acceptable if lodash _.sum() is not available.
In cases when reduce is required because the higher-level operations like map/filter aren't flexible enough, decompose the reduction operation into simpler steps and use map/filter with multiple passes, or write a traditional for..of loop.
This principle also explains why enhanced/range/of loops are preferred over counter-based `for` loops, and counter-based loops over `while`. Technically all loops can be handled by `while`, but it's seldom needed because enhanced loops handle the common case with the cleanest syntax. Reduce/while/counter-based `for` loops are antipatterns where higher-level, less powerful abstractions exists.
arr.filter(e => e % 2 === 0).map(e => e * 2)
The only advantage of the reduction as I see it is performance, but this is highly dubious and would need to be profiled for proof (I don't recall seeing removing a pass like this matter in practice). And if perf does matter, a for..of loop would be clearer and one-pass, not to mention async-compatible:
const result = [];
for (const el of arr) {
if (el % 2 === 0) {
result.push(el * 2);
}
}
Exercises like this illustrate why verbal technical job interviews are useful in the age of LLMs--a series of A/B taste preferences seems high signal and ripe for discussion: "Ah, so you're choosing reduce for perf... please describe a scenario you encountered where this made a measurable impact".
In my case, it's more of a personal preference than any measurable impact. Practically, `for...of` would be the best approach. But I like to avoid chaining and avoid manually building an array (in your example I'd replace `acc.push(el * 2);` with `return acc.concat(el * 2);` -- even though I suspect `push` might be more performant but not sure). Why do I prefer it? Can't explain. If the nanoseconds really mattered then I might change it. Otherwise it's just one of the many ways of achieving the same goal.
ggorlen · · focus · HN ↗
In cases when reduce is required because (for example) JS doesn't have a sum function, it should be kept simple. `arr.reduce((acc, el) => el + acc, 0)` is acceptable if lodash _.sum() is not available.
In cases when reduce is required because the higher-level operations like map/filter aren't flexible enough, decompose the reduction operation into simpler steps and use map/filter with multiple passes, or write a traditional for..of loop.
This principle also explains why enhanced/range/of loops are preferred over counter-based `for` loops, and counter-based loops over `while`. Technically all loops can be handled by `while`, but it's seldom needed because enhanced loops handle the common case with the cleanest syntax. Reduce/while/counter-based `for` loops are antipatterns where higher-level, less powerful abstractions exists.
[1]: <a href="https://wiki.c2.com/?PrincipleOfLeastPower" rel="nofollow">https://wiki.c2.com/?PrincipleOfLeastPower
marcta · · focus · HN ↗
FlameWolf · · focus · HN ↗
ggorlen · · focus · HN ↗
FlameWolf · · focus · HN ↗