DEV Community

Cover image for JavaScript’s Reduce Method Explained By Going On a Diet
Kevin Kononenko
Kevin Kononenko

Posted on

JavaScript’s Reduce Method Explained By Going On a Diet

If you have ever read a nutrition label, then you can understand the reduce() method in JavaScript.

The reduce method in JavaScript gives you a simple way to take an array of values and combine them into one value, or sum the array based on multiple category.

Wow, that is a lot in one sentence, so let’s take a step back here.

Of course, you can always use a for loop to iterate through an array and take a specific action on each value. But, if you do not use methods like filter(), map() and reduce(), then your code will become harder to read. Other developers will need to read each loop thoroughly to understand the purpose. And, it will create more chances for bugs, since you will need to create more variables to track individual values.

Filter methods take all the elements in an initial array, and only allow certain elements into a final array.

Map methods run a function on each element in an initial array before storing it in a final array.

And reduce methods combine elements from an initial array into a final value or array of values.

I realized that this was kind of like dieting. From very simple methods like calorie counting, to more complicated diets like Atkins or WeightWatchers, the goal is to distill all the food that you might eat over the course of the day into one value (or values) to see if you are on track to lose weight.

So let’s jump into it! In order to understand this tutorial, you just need to have experience with for loops.

Simulating Reduce With a For Loop

Here’s a quick way to show the functionality of reduce(), using a for loop. Let’s say that you have an array with the calorie counts of 5 separate foods that you have eaten over the course of the day. You want to figure out how many total calories you have consumed. Here’s the code.

let calories = [800, 200, 100, 300, 700];
let total = 0;

for (let i = 0; i< foods.length; i++){
  total += calories[i];
}

//value of total will be 2100
Enter fullscreen mode Exit fullscreen mode

It’s pretty simple. You create a variable to hold the final amount, then add to it as you run through the array. But, you still needed to introduce a new variable, and the loop gives no clues about the purpose of the loop.

A Simple Example of Reduce

Let’s learn how to accomplish the same goal with the reduce() method.

  1. Reduce is an array method so we will start with the array of calorie counts.
  2. It uses a callback function that runs on each element in the array.
  3. It uses a return statement to show how the value should accumulate after each iteration of the array.

let foods = [800, 200, 100, 300, 700];

let total = foods.reduce(function(sum, item){
  return sum + item;
});

Enter fullscreen mode Exit fullscreen mode

So, reduce has some concept of memory. As you go through each item in the array, the values are being tracked in the sum argument. In our previous example, we had to declare a new variable outside the scope of the loop to “remember” the values.

There may not seem to be too much of a difference in readability between this and for(). But, when you (or another developer) need to scan hundreds of lines of code, reduce will give you a quick hint about the purpose of the code block.

Example 2- Using Objects

We have just been looking at one-dimensional arrays thus far. But, if you can iterate through an array full of numbers, you can also iterate through an array full of objects. Let’s add a name to each element so we can figure out what we are actually eating over the course of the day.

let foods = [
  {name: 'steak', calories: 800},
  {name: 'fruit', calories: 200},
  {name: 'salad', calories: 100},
  {name: 'chips', calories: 300},
  {name: 'ice cream', calories: 700},
];

let total = foods.reduce(function(sum, item){
  return sum + item['calories'];
}, 0)

//value of total will be 2100
Enter fullscreen mode Exit fullscreen mode

You ate a steak for breakfast… with some fruit… then some salad and chips for lunch… then finally ice cream for dinner. That’s a heck of a day.

Hopefully, you can see the flow as we go through the array. As we go through each element, the sum grows to represent the total calories consumed over the day. The entire point is to put these values into one big bucket- the number of calories over the day.

Example 3- Using Mulitple Categories

So if it is all about calories, why the heck are there so many different diets? I am not going to wade into thenutrition science, but here’s the general summary- there is plenty of disagreement over the “best” way to lose weight. Some encourage you to just count calories, while others will look at protein, carbs, fat and any other number of factors.

Let’s imagine for a moment that you want to change the code so that you can evaluate your diet based on any common dieting system. You will need to track carbs and fat for each type of food. Then you will need to summarize it all at the end so you can figure out how many grams you consumed in each category. Here is our foods object with fake nutritional values.

let foods = [
  {name: 'steak', calories: 800, carbs: 10, fat: 30},
  {name: 'fruit', calories: 200, carbs: 20, fat: 0},
  {name: 'salad', calories: 100, carbs: 0, fat: 5},
  {name: 'chips', calories: 300, carbs: 10, fat: 10},
  {name: 'ice cream', calories: 700, carbs: 20, fat: 20},
];
Enter fullscreen mode Exit fullscreen mode

Now, we need to run the reduce() method. But, it can’t all be tracked in one value. We want to keep our categories. So, our accumulator needs to be an object with the same categories as the array.

Here’s a quick GIF of the process.

As you go through each item, you will change the value of a specific property within an object. If that object does not yet have a property with the correct name, you will create it. Here is the code.

let total = foods.reduce(function (buckets, item) {
  let calories = item.calories;
  let carbs = item.carbs;
  let fat = item.fat;

  buckets['calories'] += calories;
  buckets['carbs'] += carbs;
  buckets['fat'] += fat;

  return buckets;
});

/*total object is
{name: 'steak', calories: 2100, carbs: 60, fat:65}
*/
Enter fullscreen mode Exit fullscreen mode

We are using buckets as an object to categorize the values based on name of their property. We use the += operator to add to the appropriate bucket for each value within the object originally from the foods array. Notice how we don’t store the name of each food? That is because it is insignificant- we just want the numbers so we can analyze the success of your diet for the day.

As you can see, there was one issue in our output. We have a name field that is set to 'steak'. We don't want to store the name! So, we need to specify another argument- the initial value.

This argument comes after the callback, and we want to initialize the calories, carbs and fat field to 0, so that our reduce method knows that those are the only three key/value pairs we will be using for the buckets argument. You can see an example here.

let total = foods.reduce(function (buckets, item) {
  let calories = item.calories;
  let carbs = item.carbs;
  let fat = item.fat;

  buckets['calories'] += calories;
  buckets['carbs'] += carbs;
  buckets['fat'] += fat;

  return buckets;
},
{calories: 0, carbs: 0, fat:0}
);

/*total object is
{calories: 2100, carbs: 60, fat:65}
*/
Enter fullscreen mode Exit fullscreen mode

Get The Latest Tutorials

Did you enjoy this tutorial? Check out the CodeAnalogies blog to get the latest visual guides to HTML, CSS and JavaScript topics.

Top comments (13)

Collapse
 
xngwng profile image
Xing Wang • Edited

I think below is an even more easier to grok analogy for reduce.

map reduce

:)

Collapse
 
kbk0125 profile image
Kevin Kononenko

Haha yes, very clever!

Collapse
 
rattanakchea profile image
Rattanak Chea

A pic is worth a thousand words.

Collapse
 
1hko profile image
1hko • Edited

The quoted result from the last reduce

/*total object is
{calories: 2100, carbs: 60, fat:65}
*/

is actually

// { name: 'steak', calories: 2100, carbs: 60, fat: 65 }

And the input foods is mutated too... :(

reduce enables functional style so we end up with really bad headaches if mix it with imperative-style thinking (+=). The same is true for map, filter, and the rest of the functional friends.

JavaScript is a very loose language though and so it allows us to do all sorts of reckless things. I will try my best to help you. Using pure functions will avoid setting up traps that might surprise us later.

Below total is a brand new value, foods is not mutated

const total =
  foods.reduce
    // pure function, operates only on its inputs, has no side-effects
    ( (sum, food) =>
        ({ calories: sum.calories + food.calories
         , carbs: sum.carbs + food.carbs
         , fat: sum.fat + food.fat
        })

    // don't forget about initial sum
    , { calories: 0 
      , carbs: 0
      , fat: 0
      }
    )

The arguments we're passing to reduce are useful on their own. Giving them a name communicates intent more clearly and promotes reuse in other areas of our program.

const emptyFood =
  { calories: 0
  , carbs: 0
  , fat: 0
  }

const addFood = (x, y) =>
  ({ calories: x.calories + y.calories
   , carbs: x.carbs + y.carbs
   , fat: x.fat + y.fat
  })


const sumFood = (foods = []) =>
  foods.reduce(addFood, emptyFood)


console.log(sumFood(foods))
// { calories: 2100, carbs: 60, fat: 65 }

Providing the initial sum makes sumFoods a pure function, one the functional programmer can rely upon

console.log(sumFoods([]))
// { calories: 0, carbs: 0, fat: 0 }

Without an initial sum, if you try to reduce an empty array, your program will throw an error, which is a side effect, which would make your function impure, and therefore unreliable – headaches!

Collapse
 
kbk0125 profile image
Kevin Kononenko

Hey 1hko, thanks for your thorough response! I learned quite a few things from it that I didn't know :)

I wrote this as the first resource that someone should read when they want to learn reduce, so it is always tough for me to figure out how much to specify in there vs. how much they will need to learn on their own.

I added a final code block with the initial value for the object and an explanation on why you need to create an initial value. I think you are right, the previous version would have led people into a trap potentially.

Collapse
 
1hko profile image
1hko

I agree it's very challenging to determine how much information to include. It's easy to overwhelm the learner!

I'm happy to have contributed to your post. Happy to discuss any time.

Collapse
 
lucasmonstro profile image
Lucas Silva

let total = foods.reduce((sum, {calories}) => sum + calories, 0); // Single line

Collapse
 
sara__cruz profile image
Sara Cruz

I don't fully understand why there is a 0 after the parameters, could you explained please? :)

 
sara__cruz profile image
Sara Cruz

Ok! Now I understand! 🙂 thanks!!