The Reduce Method - Understanding Filter, Map, and Reduce (3/3)

The Reduce Method - Understanding Filter, Map, and Reduce (3/3)

Part 3 of a three-part series that breaks down filter, map, and reduce methods in JavaScript. This section deals with the reduce method

·

6 min read

This is the final part of a three-part series. Check out Part 1: The filter method and Part 2: The map method

The Reduce Method

Congratulations on making it this far! We've covered two powerful array methods and are now ready to conquer the final boss - the Reduce method.

The reduce method is quite different from the Filter and Map methods. Both Filter and Map had specific return values. i.e. The Filter method would return an array of elements that passed the filter condition, and the Map method would return an array of elements that were returned by its callback function for each iteration.

The Reduce method, however, doesn't have a specific return value. We, the developer, can set up the method to return what we want. It can return a number, a string, an array, an object - anything! This flexibility is what makes it so powerful.

The method is usually used to 'reduce' an input array to a single value. An example would be using the method to calculate the sum of the array elements. We're inputting an array of elements, and we are reducing the array to a single value - the sum. Hence the name.

Here's the basic structure:

//Syntax
array.reduce(callbackFunction, initialValue)

// Common usage
const reducedValue = array.reduce(function(accumulator, currentElement, indexOfElement, fullArray){
// function body
}, initialValue)

Let's break it down:

The Callback Function

The callback function has four parameters

  1. An accumulator (also referred to as 'previous value')

  2. The current array element

  3. The index of the current element

  4. The entire array

The accumulator stores the value that is returned by the callback function during each iteration. That way, the value returned in one iteration can be accessed in the subsequent iteration through the accumulator. Ultimately, the value returned by the callback function in the last iteration is what the Reduce method returns. That was a mouthful! But it will become clear as we check out a few examples.

Optional Initial Value Argument

The initialValue argument is an optional argument that is used to set the initial value of the accumulator variable.

If initialValue is not specified, the method starts iteration with accumulator = array[0] and currentElement = array[1]. The iteration starts at index 1 in this case.

If initialValue is specified, the method starts iteration with accumulator = initialValue and currentElement = array[0]. The iteration starts at index 0 in this case.

Values for the first iteration:

image.png

Examples

  1. Sum of an array: Summing an array is one of the easiest implementations of the reduce method. Here's how it goes:
const array = [9, 7, 11, 5, 8];

const sum = array.reduce((accumulator, element) => {
  const returnValue = accumulator + element;
  return returnValue;
})
console.log(sum) // 40

Since the accumulator stores the value returned from the callback function of one iteration and makes it available to the next one, we keep on adding the elements to the accumulator for each iteration.

Here's a visualization of the different variable values of the callback function for every iteration. Note that initialValue was not passed, so accumulator starts with the value at index 0.

image.png

  1. Finding the maximum value in an array
const array = [9, 7, 11, 5, 8]

const max = array.reduce((acc, el) => el > acc ? el : acc)
console.log(max) // 11

Let's break down the callback function.

  • For the first iteration, the value of the accumulator acc is 9 and el is 7

  • For each iteration, the callback function checks el against acc

  • If el > acc, el is returned, which updates the value of acc as el

  • If el < acc, acc itself is returned so that the value of acc doesn't change.

image.png

Returning is important!

A mistake that developers make is assuming that just updating the value of the accumulator inside the callback function is enough for the value to be available in the next iteration.

Here's an example -

const numbers = [100, 200, 300, 400, 500];

const sum = numbers.reduce(function(acc, el) {
    console.log(acc, el);
    acc += el;
})
console.log(sum);

/*
100 200
undefined 300
undefined 400
undefined 500
undefined
*/

For the first iteration, acc is initialized with numbers[0] and el is numbers[1]. Even though acc is updated inside the callback function, the function does not return any value. Remember - the value returned by the callback function for one iteration is the value of the accumulator in the next iteration. Since the callback doesn't return anything, the value of acc for the second iteration is undefined. Same for the subsequent iterations.

Taking it further

Reduce can also be used to implement the filter and map methods as well. That's how flexible it is.

const array = [9, 7, 11, 5]

// filter method
// number should be greater than 7 to pass the filter
const filteredArray = array.reduce((acc, el) => {
  if (el > 7)
    acc.push(el);
  return acc;
}, []);

// map method
// returns an array with the squares of the array elements
const mappedArray = array.reduce((acc, el) => {
  acc.push(el ** 2);
  return acc;
}, []);

console.log(filteredArray) // [9, 11]
console.log(mappedArray) // [81, 49, 121, 25]

In both the implementations shown above, we've used the initialValue argument to set the initial value of the accumulator to that of an empty array. This is because the final values we need from the filter and map implementations are arrays. By initializing the accumulator with an empty array, we can then push elements to it and then return the array at the end.

Have an idea of what the final value returned by the method should be and set the initial value and the return statement inside the callback accordingly.

The filter method:

Elements that pass the filter condition are pushed into the accumulator array.

image.png

The map method:

The squared value for each array element is pushed into the accumulator array.

image.png

Here's an example that returns an object. Counting occurrences of elements:

const fruits = ['Mango', 'Banana', 'Orange', 'Apple', 'Orange', 'Banana']

const occurances = fruits.reduce((acc, el) => {
  // if element is present inside the object, increase its count
  // else initialize count to 1
  if (acc[el])
    acc[el]++;
  else acc[el] = 1;

  return acc;
}, {})

console.log(occurances)
/*
{
  Apple: 1,
  Banana: 2,
  Mango: 1,
  Orange: 2
}
*/

For this example, we set initialValue as an empty object, because we want the method to return an object that gives the number of occurrences for the array items.

For each iteration, the function checks if the current element is already a key of the accumulator object. If yes, the corresponding value is increased by one. If not, a new key is created and its value is set to one. This object is then returned.


As a challenge, try implementing a function that uses the reduce method to perform both filtering and mapping. The function takes in an array of numbers and returns a new array that contains the squares of just the odd numbers inside the original array.

Conclusion

The reduce method is the swiss army knife of the array methods that can seemingly do anything. It's a handy tool when tackling complex data manipulation that wouldn't be possible with the other methods.

That wraps up the Reduce method, and this series as well. We've covered the three major array methods - Filter, Map, and Reduce, and I hope that by now you really do understand them :)

Drop a comment, a Twitter DM, or an email to ask questions or discuss anything JavaScript!

Did you find this article valuable?

Support Abin John by becoming a sponsor. Any amount is appreciated!