The Almighty JavaScript Reduce method

The Almighty JavaScript Reduce method

A detailed explanation of the reduce method in JavaScript

What is reduce

reduce() is an array method that iterates through the elements of an array and executes a reducer function for every element in the array. reduce() is the most versatile array method in JavaScript.

A quick review of how reduce works

Let's consider the typical example of adding numbers in an array using reduce():

const numbers = [ 5, 4, 3, 2, 1];
function addNumbers() {
  return numbers.reduce(
  (sum, currentValue) => sum + currentValue, 0)
}

// => 15
addNumbers();
  • Here, reduce() takes 2 arguments:

    1. The arrow function (AKA the reducer) and

    2. The initialValue, 0 (i.e - the value sum should initialize to)

  • The arrow function(the reducer function) above takes 2 parameters: the sum/accumulation and the currentValue - each value in the array starting from the first.

  • sum stores the return value of the reducer function after every iteration.

    1. After the first iteration, sum becomes sum + 5 = 5. Remember that sum was initialized to 0

    2. After the second iteration, sum becomes sum + 4 = 9, and so on...

  • The reducer function must return a value to reduce(..) on every iteration. Here, it returns (the cumulative sum + the current element) on every iteration.

  • Eventually, the elements in the array are reduced to a single value (15) by the reduce() function:

The above example is concise, but the reduce method can do much more than summing numbers as we will discover below.

Use cases of the reduce method

The preceding step-by-step procedure works in a similar manner to the examples below with some minor changes.

  • Obtain net cost of goods in an inventory

Suppose you want to get the total cost of items in a customer's cart. You can use reduce to do it like this:

const inventory = [
  { item: 'Cheeseburger', cost: 2.79 },
  { item: 'Cookies', cost: 1.99 },
  { item: 'French Fries', cost: 4 },
  { item: 'Burger', cost: 2.99 },
  { item: 'Coca-Cola', cost: 1.99 },
];


 function getTotalCost() { 
  return inventory.reduce(
  (total, currentValue) => total + currentValue.cost, 0)
 };

console.log(getTotalCost.toFixed(2));
// logs 13.76

What if the items above have a count? We slightly modify the arrow function to accommodate the frequency of the items like this:

const inventory = [
  { item: 'Cheeseburger', cost: 2.79, amt: 5 },
  { item: 'Cookies', cost: 1.99, amt: 30 },
];

let totalCost = inventory.reduce(
  (total, currentValue) => total + currentValue.amt * currentValue.cost, 0
55);

console.log(totalCost);
// logs
// 73.65

The above use-case works similarly to the number sum example. Refer to that example again if you do not understand how it works

  • Group (reduce) an array of objects:

Sometimes, you may want to work with objects that only contain certain values. With reduce, you can group objects by the values of their keys like this:

const teams = [
    { name: 'Barcelona', points: 63 },
    { name: 'Manchester United', points: 50 },
    { name: 'Bayern Munich', points: 70 },
    { name: 'PSG', points: 59 },
    { name: 'Arsenal', points: 70 },
];

const teamStatus = {
    qualified: [],
    disqualified: [],
};


// group teams by points higher than 60
function groupTeams(teamsArray) {
   return teamsArray.reduce((accumulator, currentTeam) => {

        currentTeam.points > 60
            ? accumulator.qualified.push(currentTeam)
            : accumulator.disqualified.push(currentTeam);
        return accumulator;
    }, teamStatus); 
}


console.table(groupTeams(teams));

//logs: Output below
index012
qualified{name: 'Barcelona', points: 63}{name: 'Bayern Munich', points: 70}{name: 'Arsenal', points: 70}
disqualified{name: 'Manchester United', points: 50}{name: 'PSG', points: 59}

Here's a breakdown of the code above:

  1. Here, our accumulator is assigned to the teamStatus object.

  2. Each time a team's point is greater than 60, push it into teamStatus.qualified (an array). Otherwise, push it into teamStatus.disqualified (array).

  3. return accumulator to reduce after every iteration.

NOTE: You should always name your accumulator/previous value variable according to what it does. In this case, it would have been more appropriate to call it grouping instead of accumulator since we are grouping objects not accumulating objects**.

The word accumulate implies that there is some kind of aggregation which there isn't in this case. It is only used here for demonstration purposes.

  • Deduplicate the elements in an array

    reduce can be used to remove duplicates from an array like this:

const duplicateArray = ['Tesla', 'Ferrari', 'BMW', 'BMW', 'Tesla', 'honda'];

function deduplicateArray(array) {
  return array.reduce((uniqueArray, currentBrand) => {
    if (!uniqueArray.includes(currentBrand)) {
      uniqueArray.push(currentBrand);
    }

    return uniqueArray;
  }, []);
}
console.log(deduplicateArray(duplicateArray));

// logs: ["Tesla", "Ferrari", "BMW", "honda"]

Here, we are using reduce to return an array of unique elements.

  1. reduce takes 2 args (arrow function and [ ])

  2. Therefore, uniqueArray is initialized to [] and currentBrand is initialized to the first element in duplicateArray ('Tesla')

  3. Now, the function is executed for each element, and each time currentBrand is not in uniqueArray , currentBrand is pushed into the accumulator array. Otherwise, the currentBrand is skipped.

NOTE: The arrow function returns uniqueArray to the arrow function after every iteration. If uniqueArray is not returned, JavaScript will produce an error.

  • Obtain the mean of a sequence of numbers
const numbers = [5, 19, 35, 50, 70];

function calculateMean(array) {
  return array.reduce((accumulator, currentNumber, index) => {
    if (index == array.length - 1) {
      return (accumulator + currentNumber) / array.length;
    }
    return accumulator + currentNumber;
  }, 0);

}

console.log(calculateMean(numbers))

In this example, reduce takes 3 arguments. The reduce function can take a maximum of 4 arguments: (accumulator/previous value, current value, index, array). Here, we are using the first 3. See the explanation below:

The code above says:

  1. Add all the numbers in the array until you reach the final number.

  2. If and when you reach the final number, Obtain the average (mean) of the accumulation and return it.

  • Sum positive integers in an array:
const numbers = [50, -50, 50, -50, 50];

function getPositiveSum() {
 return numbers.reduce((sum, currentValue) => {
    if (currentValue > 0) { return sum + currentValue; }
    return sum;
    }, 0)

}

console.log(getPositiveSum());
// logs
// 150

You can also modify the function above to perform some other type of operation (e.g. double, square), etc on all negative, odd, or prime numbers as the case may be.

Notice that this operation can be done with the filter() method too.

Below is the breakdown of the code above,

  1. reduce takes 2 arguments (function and initialValue). The function must not necessarily be an arrow function, however, arrow functions are generally preferred when using reduce.

  2. The arrow function takes 2 parameters (sum and currentValue).

  3. sum variable is initialized to the second argument of reduce (0).

  4. currentValue is the first element in our array (50)

  5. The function is executed for each element in our array like this:

    1. If the currentValue in the array is greater than 0, add the currentValue (50) to sum (0)

    2. Reassign sum to the result of the previous operation (i.e sum = 0 +50)

    3. Repeat this operation until it is executed for the last element in the array.

  • Obtain max and min numbers in an array

Can you spot the mistake in the code below? This is a common error that coders make when using reduce with objects.

const StudentResult = [
  { subject: 'math', marks: 78 },
  { subject: 'physics', marks: 80},
  { subject: 'biology', marks: 93},
  ];

console.log(
  StudentResult.reduce( (sum, curr, i) => sum.marks + curr.marks, 0))

// logs:
// cannot read properties of undefined error

sum is initialized to 0 on the first iteration, but sum.marks is undefined since we initialized sum to 0. 0 is a number, not an object, hence, sum.marks will be undefined. marks is accessed through the curr variable which is initialized to the first array object. sum only exists for one thing - to hold the result of every previous iteration which is a number, not an object.

Replace sum.marks with sum to fix the code.

There are many more incredible things that can be achieved with reduce. Those covered in this article are the most common use cases you will likely require in your day-to-day coding.

Some caveats of reduce

Some of the common mistakes coders make when using reduce() include:

  • Not returning a value from the arrow function

Always ensure that your arrow function returns a value. The return keyword can be left out if the arrow function contains a one-line statement. Otherwise, an implicit return is required.

const myArray = [ 4, 3, 2, 1 ];
// arrow function with a one-line statement
// logs: 10
myArray.reduce((sum, currentValue) => sum + currentValue, 0)

const myArray = [ 4, 3, 2, 1 ];
// arrow function with a multi-line statement
// logs: 6
myArray.reduce((sum, currentValue) => { 
    if (currentValue % 2 == 0) { return sum + currentValue}
    return sum;
}, 0)
  • Always ensure that your reducer function returns a value. Otherwise, the reduce method will throw an error.

  • Always give appropriate names to the parameters of your reducer function. For instance, ( grossIncome, currentIncome => ... ) instead of ( sum, current ) (previous, current) or (accumulator, currentValue). It's OK to use those when appropriate but you should always name your parameters based on how you intend to use them.

  • Always ensure that you supply an initial argument to the previous value (accumulator) variable to avoid errors.

Tip: Every time you want to do something that involves using both filter() and map(), consider using reduce(). It is quicker than when you combine those two since it will traverse the array only once instead of twice. However, if you prioritize readability over performance you should avoid using reduce.

Proposed group() method

In one of the use cases of reduce above, we grouped items in an array according to some criteria. TC39 - the committee responsible for the standardization of JavaScript is proposing a group array method that simplifies the grouping of items in an array via a built-in method that eliminates the need for using reduce for such grouping purposes.

This method is still in its early stages of development and it currently works on Safari browser only, therefore, it's not suitable for development at the moment. However, you could use this polyfill to use it in your project.

group() greatly simplifies the grouping of items by abstracting implementation details, and eliminating the need for a custom function. Visit MDN to read more about this new method

const array = [1, 2, 3, 4, 5];

// `Object.groupBy` groups items by arbitrary key.
// In this case, we're grouping by even/odd keys
Object.groupBy(array, (num, index) => {
  return num % 2 === 0 ? 'even': 'odd';
});
// =>  { odd: [1, 3, 5], even: [2, 4] }

const teams = [
    { name: 'Barcelona', points: 63 },
    { name: 'Manchester United', points: 50 },
    { name: 'Bayern Munich', points: 70 },
    { name: 'PSG', points: 59 },
    { name: 'Arsenal', points: 70 },
];

// group by points higher than 60

Object.groupBy(teams, (teamStatus, index) => {
  return teamStatus.points > 60 ? 'qualified' : 'disqualified';
});

Use reduce with caution

reduce() is a powerful method indeed and it is a shame that not many JavaScript developers utilize it for its more sophisticated uses.

That said, with great power comes great responsibility, hence, a lot of programmers avoid using it due to its error-proneness coupled with its not-so-friendly readability. Thus, reduce should be used at one's discretion.

You may have realized that most of the examples in this article can be rewritten with methods like filter(), find(), map(), group(), etc. Hence, they should be used instead of reduce whenever possible. The major goal of this article is to expose you to the all-round nature of the reduce method and how it can be used to carry out extensive array operations

Conclusion

In this article, we have covered the reduce() method, how it works, and some of its interesting use cases. Visit MDN to know more about the reduce method.

Thanks for reading! See you next time