Expressing the Power of JavaScript .reduce method through simple explanations

Gabriele Cimato
9 min readJan 21, 2018

--

There was a time when I was fairly new to JavaScript where the .reduce method was hard to understand, its true potential was even harder to grasp. I have been reading about it, but no one was actually showing what can be achieved with low effort with what is now one of my favorite JavaScript methods!

As the name suggests, when using this method you express an intention to reduce a set of data into a single value. Let’s dig into some code examples but first of all a quick link to its documentation . In some of the examples you might see me use new (not so much now) JavaScript features like arrow functions =>and spread syntax ..., I assume you are fairly familiar with them, if not here spread syntax is explained and here are arrow functions.

.reduce is a method that can be called on Arrays, it takes two arguments:

  • A function: called once for every element in the array, it helps determine the final value that is returned from the .reduce operation (as just mentioned, we want to go from a set of data to a single value). What this means is that every time this function is called, a return statement determines what the final value should be, based on: the current item, the final value we have so far. Let’s see an example:
const fruits = ['banana', 'coconut', 'strawberry', 'apple'];const longestFruit = fruits.reduce(
(longestFruitThisFar, curFruit) => {
if (curFruit.length > longestFruitThisFar.length) {
return curFruit;
}
return longestFruitThisFar;
}
);
// longestFruit is now 'strawberry'

The naming of variables should help you understand what is going on. Given an Array of fruits, I want to find the one with the longest name. Let’s break down each iteration of the .reduce method:

  1. longestFruitThisFar is banana 🍌, the simple reason being that when .reduce calls the function the first time, the first parameter will correspond to the first value in the array (although we can specify our own, more on this later), the second one will be the next value in the fruits Array, in this case coconut . At this point we are wondering if coconut is longer than banana, if that’s the case we use a return statement to let our reduce method know that our new final value is coconut.
  2. longestFruitThisFar is coconut, this is coming from our previous iteration where we decided that coconut was longer than banana. At this point curFruit should have the value of the next element in the Array which is strawberry. Again strawberry wins and is now our new longestFruitThisFar.
  3. longestFruitThisFar is strawberry, again this is coming from our previous iteration where we returned strawberry. Now we are going to compare it with the next value which is apple, unfortunately apple is too short (the if statement is now false), so strawberry wins and is the returned value.

At this point we have scanned the whole array, and the last value returned is strawberry, this means that longestFruit is now officially strawberry 🍓! This is a very typical use for .reduce , the beauty is that in the function we pass as a first parameter (the one where we do the comparisons), we can have any kind of logic we need from a very simple < or > comparison to more intricate logic. To be honest this is already enough to make your code do wonderful things ✨, but since we want to do even more, let’s keep digging and explore what the second argument is for.

  • The second argument is the initial value, this is optional but is fundamental for certain use cases (we will see more examples on this). Following our example, instead of starting our .reduce with banana as the longestFruitThisFar inthe first iteration, we could have had (let’s say) a custom value, maybe one coming from user input. Let’s see it in action:
// Let's assume the user passed in 'orange' because he/she wants to
// know if that fruit is longer than any other we already have.
// Such value is saved in the variable named userFruit.
const fruits = ['banana', 'coconut', 'strawberry', 'apple'];const longestFruit = fruits.reduce(
(longestFruitThisFar, curFruit) => {
if (curFruit.length > longestFruitThisFar.length) {
return curFruit;
}
return longestFruitThisFar;
},
userFruit,
);

The behavior would be similar to the one we have seen so far but, instead of having banana as longestFruitThisFar in the first iteration, we would have orange (the value passed by the user) as the first longestFruitThisFar, in this iterationcurFruit would be banana, and the same logic would be applied as we did before, iterating through the whole array. In this case strawberry would win again, but at least we gave the user a chance 😉

If you are comfortable with what expressed so far, we can go further and explore more possibilities, otherwise I would recommend to come here later and practice what we have learned. You can try to use .reduce to find the maximum value in an array of numbers, or sum all of the numbers in an array.

DIGGING A LITTLE DEEPER

What I think makes .reduce a method with special powers 💪, is its flexibility. If you have used JavaScript for a while you should be familiar with other widely used methods such as .map and .filter. Not only you can easily replace them with .reduce but you can replace many combinations of them! You heard it right!

Do you remember when I mentioned that using an initial value (the optional argument) could lead you to great things ? Now we can finally see why. The .reduce method can not only be used to reduce a set of data to a single value, but also to build a new set of data given a set of data (whoooah that’s confusing). What if, at each iteration, instead of replacing or creating a new returned value (which eventually will be the final value) we add something to it ?

Let’s see an example for each of these cases, the mindset should be the following: given an array I want to build a new array where every element is a different version of its original form (again quite confusing 🤔). Some code will help clarifying this:

// Let's use .reduce to replace .map, given an array of fruits
// I want to map each item to its uppercase version. In other
// words I want to build a new array with uppercase versions
// of fruits in the original array.
const fruits = ['banana', 'coconut', 'strawberry', 'apple'];const uppercaseFruits = fruits.reduce(
(myNewUppercaseFruits, curFruit) => {
return myNewUppercaseFruits.concat(curFruit.toUpperCase());
},
[],
);

I will explore only the first iteration. Did you notice that we are passing an empty array to use as initial value ? Do you remember what that entails in the first iteration ? You should know the answer by now, if not it’s alright “repetita iuvant”.

On my first iteration myNewUppercaseFruits is an empty array, curFruit is banana. As a return value we grab the upper case fruits we have so far (in this case it’s just an empty array) and add the uppercase version of banana. At this point myNewUpperCaseFruits will be simply ['BANANA']. Each other iteration will read the next item in the fruits array and put its uppercase version in our myNewUppercaseFruits. At the end we will have ['BANANA', 'COCONUT', 'STRAWBERRY', 'APPLE'].

Do we wan to try replace .filter with .reduce? Why not!

// Let's use .reduce to replace .filter, given an array of fruits
// I want to filter only the ones that are longer than 5 letters.
// In other words I want to build a new array that only has
// fruits with a length greater than 5.
const fruits = ['banana', 'coconut', 'strawberry', 'apple'];const longerFruits = fruits.reduce(
(myNewLongerFruits, curFruit) => {
if (curFruit.length > 5) {
return myNewLongerFruits.concat(curFruit);
}
return myNewLongerFruits;
},
[],
);

Easy as that! At each iteration I try to add a fruit only if its length is greater than 5, otherwise I don’t add anything and move along. So starting from an empty array (the optional second argument of .reduce), at each iteration I return a new version of such array that includes what I added so far, plus (and this is done through the .concat method) another fruit if the condition is satisfied.

I feel like by now you already know where I’m going with this 😜. If I wanted to uppercase every fruit and then filter only the ones whose length is greater than 5, intuitively I would use a .map and then a .filter or vice versa. So I would loop through my whole array twice! But do I really need to ? The answer is no! But HOW? Here you go:

// Let's use .reduce to replace .filter AND .map, given an array of // fruits I want to filter only the ones that are longer than 5
// letters and uppercase them.
// In other words I want to build a new array that only has
// fruits with a length greater than 5 whose name is now uppercase!
const fruits = ['banana', 'coconut', 'strawberry', 'apple'];const longerUppercaseFruits = fruits.reduce(
(myNewLongerUppercaseFruits, curFruit) => {
if (curFruit.length > 5) {
return myNewLongerUppercaseFruits
.concat(curFruit.toUpperCase());
}
return myNewLongerUppercaseFruits;
},
[],
);

This is amazing! So what we learned so far is that we can replace .map with .reduce, we can replace .filter with .reduce but most importantly we can replace many combinations of .map and .filter with a single .reduce!

This method is so powerful and embraces many functional aspects (maybe I will write about its functional aspects in more detail in another article). If I did not loose you along the way I hope you learned a lot from it, sometimes getting comfortable with .reduce can be difficult at first but with some practice it will come to you naturally!

BONUS: Insert and Update with .reduce!

As a bonus I will show you another powerful use of .reduce. Let’s say we have a cart of items, our goal is to add a new item to the cart only if we don’t have it already, otherwise we just update the quantity. Let’s roll:

const cart = [
{ name: 'banana', quantity: 5 },
{ name: 'cheese', quantity: 2 },
{ name: 'bread', quantity: 1 },
];
// Let's assume the user wants to add 2 bananas and 1 watermelon,
// I would expect his/her items to be represented in the form of
// an array of objects just like our cart.
const userItems = [
{ name: 'banana', quantity: 2 },
{ name: 'watermelon', quantity: 1 },
];
// Now I want to update the bananas quantity and add a watermelon
// I can do that with a simple .reduce
const updatedCart = userItems.reduce(
(newCart, curItem) => {
const curItemIndex = newCart
 .findIndex(i => i.name === curItem.name);
if (curItemIndex >= 0) {
return [
...newCart.slice(0, curItemIndex),
{
name: curItem.name,
quantity: curItem.quantity + newCart[curItemIndex].quantity,
},
...newCart.slice(curItemIndex + 1),
];
}
return [...newCart, curItem];
},
cart,
);

I have seen around many other examples of an insert/update that were very hard to read. Once you are comfortable with .reduce this feels more natural (and functional since we don’t have side effects or mutations). The way I read this is: starting with a cart (our optional second parameter), go through each item of the user (which is the array we are running our .reduce on), try to find such item in the existing cart (using .findIndex), if I find it then just update its quantity, if not then add it to the cart.

There are plenty other great uses of .reduce in the wild out there, probably much more interesting than adding stuff to a cart or playing with fruits 😄! I wanted to explore the power and beauty of such method with you and make it easier to get a grasp on what you can achieve with it. I am by no means a master of JavaScript but I wish I had more pointers in the past that I could have leveraged in my web apps to make my code easier to understand. If you find something not accurate in the article let me know and I’ll try my best to make it right. If some of the concepts are still hard to understand leave a comment, I can always revisit the article to make it more clear.

This article follows my new resolution for 2018 to share what I learned and along the way learn something new myself. It’s the first more technical post so I hope you are not too disappointed, if I helped even one person that would make my day!

Now go ahead and have fun with .reduce , feel free to share all the wonderful things you were able to do with it! Happy coding!

--

--

Gabriele Cimato

Lead Engineer at MongoDB, advocate of critical thinking and mindful leadership. Lots of ❤️ for the Web, Go & JavaScript! I make pizza and read books.