Understanding JavaScript reduce

The reduce method scares many JavaScript beginners, and I was one of them. Designed to obtain one single value from some or all the elements inside an array, understanding how to use it can be harder than actually implementing it from the ground up.

For this reason, I’ve decided to show you how you can understand JavaScript reduce by actually creating your own.

Let’s create your own reduce method

Let’s say you have an array of numbers, and you want to compute the sum of every number inside the array.

const numbers = [1, 3, 63, 56, 18, 73, 47, 12] // their sum is 273

If you’re comfortable with JavaScript basics such as functions and loops, it’ll be easy to come up with something like this:

function sumAllNumbers(arr) {
  let result = 0
  
  for (let i = 0; i < arr.length; i++) {
    result = result + arr[i]
  }
  
  return result
}

const sum = sumAllNumbers(numbers) // 273

What this function does is to create a variable named result and assign the value 0 to it. It then proceeds to add the value of all the numbers inside the array with a simple sum, returning the final result.

Let’s now say that you don’t want to add all the numbers inside the array, but only the even ones. So you add a simple if:

function sumAllEvenNumbers(arr) { // function name was changed
  let result = 0
  
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] % 2 === 0) {
        result = result + arr[i]
    }
  }
  
  return result // result will be 86
}

const sum = sumAllNumbers(numbers) // 86

So what we’re doing now is to check, before actually touching result’s value, if the remainder between any given array value and 2 is 0, one of the ways you can understand if a number is even.

What if you want to add only odd numbers? Multiples of 11? All the values bigger than 10? Would you need to create a different function for every one of these cases? There has to be a better way. We could start to generalize the operation of adding some or all the elements inside an array, and we could do this using functions.

Let’s take the even adding logic from the for loop to a function on its own:

/* This function adds b to a only if b is even. */
function sumEvenNumbers(a, b) {
  let result = a;
  if (b % 2 === 0) {
    result = result + b;
  }

  return result;
}

This function is an independent piece of logic that returns a or a + b if b is even. Let’s use it back in our main function:

function sumAllEvenNumbers(arr) {
  let result = 0
  
  for (let i = 0; i < arr.length; i++) {
    result = sumEvenNumbers(result, arr[i])
  }
  
  return result
}

const sum = sumAllEvenNumbers(numbers) // 86

We have abstracted away the operation of checking if a number is even before adding it to the overall result thanks to a function designed to operate on the overall result of the operation result and the current element arr[i] of the array.

What if, somewhere else in our project, we need to sum all the odd numbers inside the array? We could create another helper function, similiar to the previous one:

/* This function adds b to a only if b is odd. */
function sumOddNumbers(a, b) {
  let result = a;
  if (b % 2 !== 0) {
    result = result + b;
  }

  return result;
}

What about the main function? Should we duplicate it in order to use a different helper than before?

function sumAllOddNumbers(arr) {
  let result = 0
  
  for (let i = 0; i < arr.length; i++) {
    result = sumOddNumbers(result, arr[i])
  }
  
  return result
}

We could, but there is a better approach. In JavaScript, functions can be passed as arguments to other functions. This means that we could adapt our main function to accept the helper function as argument, allowing us to re-use it for many more kinds of operations as long as we’ll be able to create the according helper:

const numbers = [1, 3, 63, 56, 18, 73, 47, 12]

function sumNumbersWithHelper(arr, helper) {
  let result = 0
  
  for (let i = 0; i < arr.length; i++) {
    result = helper(result, arr[i])
  }
  
  return result
}

const sum = sumAllNumbers(numbers, sumAllOddNumbers) // 187

This way, as long as we design our helper functions to operate on these two arguments — the overall result of the operation, and the current array element — we’ll be able to perform all the kinds of sums. We could create a sumNumbersGreaterThan10 helper, and throw it right into the main function call: sumNumbersWithHelper(numbers, sumNumbersGreaterThan10).

This is great, but we’re forced to create helper functions that only work with sums. What if we want to multiply all the numbers inside an array this time? Creating a simple helper won’t work:

/* This function multiplies a and be without any check. */
function multiply(a, b) {
  return a * b
}

const numbers = [1, 3, 63, 56, 18, 73, 47, 12]
const multiplication = sumNumbersWithHelper(numbers, multiply) // we get 0

Why? Let’s take a look at the main function:

function sumNumbersWithHelper(arr, helper) {
  let result = 0
  
  for (let i = 0; i < arr.length; i++) {
    result = helper(result, arr[i])
  }
  
  return result
}

Notice anything? Our starting result is 0! This will ruin our operation, because every number multiplied by 0 is still 0 and we’ll always have 0 as result. We can easily fix this by providing the starting value of our operation as another argument of the main function:

function sumNumbersWithHelper(arr, helper, startingValue) {
  let result = startingValue
  
  for (let i = 0; i < arr.length; i++) {
    result = helper(result, arr[i])
  }
  
  return result
}

This way we’ll be able to use 1 as starting value, and our function will be flexible enough to compute multiplications too:

const multiplication = sumNumbersWithHelper(numbers, multiply, 1)

Congratulations, you’ve just built your own reduce function! Why is that? Because this function is now powerful enough to perform all kinds of cumulative operations obtaining a single value from an array. Using the right terminology, the “helper” function is actually the reducer function, and the “overall result of the operation” is the accumulator value, responsible of holding the value of the operation at every iteration.

function reduce(arr, reducer, startingValue) {
  let accumulator = startingValue
  
  for (let i = 0; i < arr.length; i++) {
    accumulator = reducer(accumulator, arr[i])
  }
  
  return accumulator
}

Of course, the syntax of the original reduce is different: since it is a method, you don’t need to pass the array as an argument. You just call the method on the array you need to operate on, and pass your reducer:

const numbers = [1, 3, 63, 56, 18, 73, 47, 12]

function sumEvenReducer(acc, curr) {
  let result = acc
  if (curr % 2 === 0) {
    result = result + curr
  }
  return result
}

const sum = numbers.reduce(sumEvenReducer, 0) // 86

Except for the “arr” argument, now gone, we still need to pass the reducer function. The initial result of our operation is optional: when absent, the function will use the first element of the array as initial result. Of course, you’re not limited to work with numbers. The more you use reduce the more use cases will come to your mind.

Recap

JavaScript reduce method is useful to perform operations involving more or all of the elements inside an array, obtaining a single value. It is a higher order function, meaning that it takes another function as one of its arguments. This function argument, the reducer, will receive some arguments by the main reduce function: the accumulator value holding the result of the global operation while the function builds it between each iteration, the current array value and optionally the current value index. You can find all the details and other examples on the MDN reference.