DEV Community

Joe Pea
Joe Pea

Posted on

You don't need Array.reduce()

I was reading another dev.to post, Demystifying Array.reduce(), but I didn't feel convinced about using Array.reduce().

Maybe I too am not using Array.reduce() the right way, but every time I do, I end up disliking it and switching to a simple for..of loop.

Up ahead are the three examples from that article, converted to use for..of and in my opinion easier to read and cleaner.

Take for example the sum example:

const array = [1, 2, 3, 4];
const sum = array.reduce((accumulator, currentItem) => {
    return accumulator + currentItem;
}, 0);

// sum = 10

It can be written as

const array = [1, 2, 3, 4]
let sum = 0
for (const n of array) sum += n

// sum = 10

That's simpler!

The next example,

const trips = [{type: 'car', dist: 42}, {type: 'foot', dist: 3}, {type:'flight', dist: 212}, {type: 'car', dist: 90}, {type: 'foot', dist: 7}] 

const distanceByType = trip.reduce((out, curr) => {
    const { type, dist } = curr;
    if (out[type]) {
        out[type] += dist;
    } else {
        out[type] = dist;
    }
    return out;
}, {});

// distanceByType = {car: 132, foot: 10, flight: 212};

can be rewritten as

const trips = [{type: 'car', dist: 42}, {type: 'foot', dist: 3}, {type:'flight', dist: 212}, {type: 'car', dist: 90}, {type: 'foot', dist: 7}] 
const distanceByType = {}

for (const trip of trips) {
  const { type, dist } = trip
  if (distanceByType[type]) {
    distanceByType[type] += dist
  } else {
    distanceByType[type] = dist
  }
}

// distanceByType = {car: 132, foot: 10, flight: 212}

Simple!

Finally, the example from the comments about piping functions,

const pipeOnce = (fn1, fn2) => (args) => (fn2(fn1(args)));
const pipe = (...ops) => ops.reduce(pipeOnce);

const addTwo = a => a + 2;
const mulTwo = a => a * 2;

const addTwoMulTwo = pipe(addTwo, mulTwo);
console.log(addTwoMulTwo(1));  // (1 + 2) * 2 => 6
console.log(addTwoMulTwo(2));  // (2 + 2) * 2 => 8
console.log(addTwoMulTwo(3));  // (3 + 2) * 2 => 10

is a better of example of reduce, but it can be written as

const addTwo = a => a + 2;
const mulTwo = a => a * 2;
const addTwoMulTwo = n => mulTwo(addTwo(n))

console.log(addTwoMulTwo(1));  // (1 + 2) * 2 => 6
console.log(addTwoMulTwo(2));  // (2 + 2) * 2 => 8
console.log(addTwoMulTwo(3));  // (3 + 2) * 2 => 10

If we want to pipe an arbitrary number of functions, we can do it with for..of too:

const pipe = (...fns) => arg => {
  for (const fn of fns) arg = fn(arg)
  return arg
}

const addTwoMulTwo = pipe(addTwo, mulTwo)

This one isn't as short, but it is easier to understand.

What are some use cases where Array.reduce() really shines over alternatives like for..of?

Please share if you know!

Top comments (28)

Collapse
 
pureooze profile image
Uzair Shamim

One of the reasons I like using reduce is because it prevents accidental mutation of the the original array and it is consistent with how it works and looks (in fact this applies to the other array methods like map and filter).

Collapse
 
joelnet profile image
JavaScript Joel • Edited

for loops either mutate data or perform side effects, which as a general rule why they are good to avoid.

map, filter, and reduce also allow better code reuse and testability because it can be broken down further than a for loop can.

Example:

// modular, reusable, testable!
const add = (a, b) => a + b

// readable!
const sum = array.reduce(add)
Enter fullscreen mode Exit fullscreen mode

You'll also notice all those lets go away and become const.

Get rid of for loops and it's possible to write your entire program without a single var or let!

Collapse
 
elitelabyrinth profile image
Average Joe

But if you don't pass any initial value then there would be errors in case of empty arrays. Example:

Image description

Collapse
 
crazy4groovy profile image
crazy4groovy

I think you mean

const add = (a, b) => a + b
const sum = array.reduce(add, 0)

You need a seed value.

Collapse
 
joelnet profile image
JavaScript Joel

Nope. That is exactly what I meant.

The seed is optional. If you don't provide a seed, the first item in the array is given as the accumulator.

Run the code!

const add = (a, b) => a + b

[1, 2, 3].reduce(add) //=> 6
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
fregante profile image
Fregante • Edited

Sum is just about the only case where reduce is useful, but in reality it should never be passed around like that. Why keep an add function around that is only meant to be used together with a reduce? Just wrap it once and never use reduce again:

const sum = (array) => array.reduce((a, b) => a + b);

sum([1, 2, 3]) //=> 6
Enter fullscreen mode Exit fullscreen mode

Don't tell me arr.reduce(add) makes more sense than sum(arr) because it doesn't.

In reality you can write sum event more efficiently with a regular loop and every part of your code benefits.

Collapse
 
seniorjoinu profile image
Alexander • Edited

Seriously, man? for..of?

const myTotalLikes = retrieveAllPosts()
    .filter(p => p.user == me)
    .map(p => p.likes)
    .reduce((acc, like) => acc + like, 0)
Enter fullscreen mode Exit fullscreen mode

Sometimes it's better to use for..of (e.g. when you need some boost iterating over a huge collection you can do all the stuff in one loop) but functional style is much more useful when you are trying to express yourself to your colleagues.

Collapse
 
lewiscowles1986 profile image
Lewis Cowles

My head nearly exploded looking at that. Map reduce is not a good pattern for application engineering, it's a backend big-unstructured-data pattern.

retrievePostsFor(me) would be so much easier to think about as well. Baking in getting all posts and filtering is just not clear unless you have a shallow micro-service code-base, embrace the cascade.

Finally wrap that up in a method

function totalLikes(user) {
    return likes = retrievePostsFor(user)
        .map(p => p.likes)
        .reduce((sum, likes) => sum + likes, 0);
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lewiscowles1986 profile image
Lewis Cowles • Edited

to anyone liking this, it works better if you just send in users. Then it doesn't matter where they come from, and anything with 0 likes just adds items to the list...

In-fact it doesn't need to be a user at all. Just something implementing a likeable interface, which has a method to retrieve likes.

Collapse
 
piotrpalek profile image
Peter

I'd agree maybe it's that I'm used to for loops but they read easier for me and I know better what's going on.

I'd also say it's the same for the majority of JS users (at least from my experience) so when you got a bigger team the safer bet is to use for loops. Helps to make the code easier to understand for more people.

I would say that the absence of side effects is often overvalued when talking about this. Though I understand where people are coming from :)

Collapse
 
enriquemorenotent profile image
Enrique Moreno Tent

Your first example can be solved like this:

add = (a, b) => a + b
array.reduce(add, 0);
Enter fullscreen mode Exit fullscreen mode

Your second example can be solved like this:

getDist = (obj, type) => type in obj ? obj[type] : 0

addTrip = (acc, trip) => ({ ...acc,
    ...{[trip.type]: (getDist(acc, trip.type) + trip.dist)}
})

trips.reduce(add, {})
Enter fullscreen mode Exit fullscreen mode

Both example look easier with reduce, I think

Collapse
 
trusktr profile image
Joe Pea

Those are shorter, but at a glance it still takes me more time to understand it than the for..of loops. Maybe it's just how my mind is trained.

Collapse
 
ivanperez profile image
Iván Pérez

You are creating one object per iteration! This can't perform well with large arrays...

Collapse
 
euler2718 profile image
John Corley • Edited

I'm guessing map, filter, fold, reduce etc were born from adopting functional programming methods. Sounds like you're not really into that style (while I totally agree about lack of side effects and mutation). It sounds kind of weak, but I would say to truly get to the heart of your question, work with some Haskell or purescript (or look into lambda calculus), instead of trying to compare JavaScript code.

Collapse
 
kayis profile image
K

Maybe ReasonML could help too, because it's closer to JavaScript.

Collapse
 
jtenner profile image
jtenner

I use Array.prototype.reduce a lot. The reason why is because v8 and firefox does a very good job of optimizing reduce calls when it gets optimized.

Often in the case of clarity over speed, using for..of loops is the best. Sometimes, it's not always about clarity.

Collapse
 
trusktr profile image
Joe Pea

Good point. Thanks for that perspective!

Collapse
 
nicowernli profile image
Nicolás Wernli

First example could be simpler with reduce, I mean, if you use n as variable in the for version, why not using n in the reduce one?

const array = [1, 2, 3, 4];
const sum = array.reduce((a, n) => a + n, 0);

Collapse
 
theodesp profile image
Theofanis Despoudis

Actually, this is better as you can extract the reducer part
(a, n) => a + n as a function. The other way is not reusable.

Collapse
 
qm3ster profile image
Mihail Malo

It also helps a lot when one has a phobia of statements.

const panic = err => {
  throw err // oh no, a statement!
}
const apply = (state, event, context) =>
  (this.reducers[event.type] ||
    panic(new Error(`Unknown event type: ${event.type}`)))(
    state,
    event,
    context
  )

const applyAll = (state, events, context) =>
  events.reduce((state, event) => apply(state, event, context), state)

const replay = (events, context) => applyAll(this.initialState, events, context)

Collapse
 
andrewzhurov profile image
Andrew Zhurov

Is this correct?

const pipeOnce = (fn1, fn2) => (args) => (fn2(fn1(args)));
const pipe = (...ops) => ops.reduce(pipeOnce);

It seems for me pipeOnce should be

(acc, fn) => fn(acc)

Though it would not be 'pipeOnce' by semantic anymore:)

Collapse
 
trusktr profile image
Joe Pea

Heh, well this shows that thinking about it is more complicated than the for..of loops.