3 Simple Snippets to Help You Understand Array .reduce()! 🥳

harrison_codes

Harrison Reid

Posted on February 4, 2020

3 Simple Snippets to Help You Understand Array .reduce()! 🥳

If you find this post useful, you can follow me on twitter, sign up to my mailing list or check out the other posts on my blog. I've also got a couple of active side projects that you might like to check out:

  • ippy.io - An app for creating beautiful resumes
  • many.tools - A collection of useful utilities for designers and devs

Are you having trouble getting your head around the JS Array .reduce() method?

If so, don't worry - you're not alone. For whatever reason, the reduce method seems to take a while to click for many developers. I've been in this boat myself. Fortunately, once you break things down, it's not too complex.

My aims for this article are to:

  1. Offer what I think is a useful mental model for thinking about .reduce().
  2. Provide a series of usage examples to strengthen your understanding

Let's get started.

The Mental Model

Reduce is often introduced as a way of reducing an array to a single value. While this is indeed what it does, I've never found this to be particularly helpful in understanding how it works, or what I might be able to do with it.

For me, the simplest way to think about .reduce() is as a fancy case of .forEach(). The essentials are similar:

  • We provide a callback function
  • We iterate over the array, and execute the callback function once per array item.

The important differences are that with .reduce():

  • Each invocation of the callback is aware of the return value of the previous invocation (or on the first call, the initial value we've provided).
  • Once there are no more items in the array, the return value of the last callback invocation is returned as the final result of the .reduce() call.

In fact, we can implement a reduce function ourselves using a slim wrapper around .forEach():


const reduce = (array, callback, initialValue) => {
  // Originally stores the initialValue then updated
  // on each iteration with the return value of the callback.
  let previousReturnValue = initialValue

  // Iterate over the array items, updating the currentReturn value at each step.
  array.forEach((item, index) => {
    const result = callback(previousReturnValue, item, index, array)
    previousReturnValue = result;
  })

  // Return the final value once all array items have been iterated over
  return previousReturnValue
}


Let's now look at some examples of using .reduce(), and step through the execution path.

1. Adding an array of numbers

In this common example, we're adding an array of numbers to arrive at a final sum:


const initialValue = 0

const numbersToAdd = [ 1, 2, 3, 4 ]

const addFunction = (runningTotal, numberToAdd) => {
  return runningTotal + numberToAdd;
}

const sum = numbersToAdd.reduce(addFunction, initialValue)

console.log(sum)
// => 10

// =======================================================

// How was this calculated? Lets step through it:

// The addFunction callback is invoked for each array item:

// -- FIRST CALLBACK INVOCATION -- 
// Array Item => 1
// Previous return value => first invocation (so the initialValue is used)
// Callback invocation => numbersToAdd(0, 1)
// Callback return value => 1

// -- SECOND CALLBACK INVOCATION --
// Array Item => 2
// Previous return value => 1
// Callback invocation => numbersToAdd(1, 2)
// Callback return value => 3

// -- THIRD CALLBACK INVOCATION --
// Array Item => 3
// Previous return value => 3
// Callback invocation => numbersToAdd(3, 3)
// Callback return value => 6

// -- FOURTH (AND LAST) CALLBACK INVOCATION --
// Array Item => 4
// Previous return value => 6
// Callback invocation => numbersToAdd(6, 4)
// Callback return value => 10

// Final Result: 10


2. Finding the largest of an array of numbers

Here we'll use reduce to find the maximum value in an array.

This is a slightly special case, in that we haven't provided .reduce() with an initial value argument. Because of this, .reduce() skips the callback for the first array item, and instead uses it as the initial value for the callback invocation on the second array item.


const numbers = [ 10, 40, 4, 50, 101 ]

const findLarger = (currentMax, numberToCheck) => {
  if (numberToCheck > currentMax) {
    return numberToCheck;
  }
  return currentMax;
}

const largest = numbers.reduce(findLarger)

console.log(largest)
// => 101

// =======================================================

// How was this calculated? Lets step through it:

// The findLarger callback is invoked for each array item:

// -- FIRST CALLBACK INVOCATION -- 
// Array Item => 10
// Previous return value => first invocation, and no initialValue provided
// Callback invocation => Not Invoked
// Callback return value => N/A

// -- SECOND CALLBACK INVOCATION --
// Array Item => 40
// Previous return value => nothing, however first array item will be used as the initialValue
// Callback invocation => findLarger(10, 40)
// Callback return value => 40

// -- THIRD CALLBACK INVOCATION --
// Array Item => 4
// Previous return value => 40
// Callback invocation => findLarger(40, 4)
// Callback return value => 40

// -- FOURTH CALLBACK INVOCATION --
// Array Item => 50
// Previous return value => 40
// Callback invocation => findLarger(40, 50)
// Callback return value => 50

// -- FIFTH (AND LAST) CALLBACK INVOCATION --
// Array Item => 101
// Previous return value => 50
// Callback invocation => findLarger(50, 101)
// Callback return value => 101

// Final Result: 101


3. Categorising an array into even/odd numbers.

Of course, you're not restricted to returning numbers from the callback in .reduce(). You can return anything you want. In this case, we return an object that is categorising the array values into even/odd buckets.


const initialValue = { even: [], odd: [] }

const numbersToCategorise = [1, 3, 4, 8, 10]

const categorisingReducer = (categories, numberToCategorise) => {
  const isEven = numberToCategorise % 2 === 0
  if (isEven) {
    categories.even.push(numberToCategorise)
  } else {
    categories.odd.push(numberToCategorise)
  }
  return categories
}

const categories = numbersToCategorise.reduce(categorisingReducer, initialValue)

console.log(categories)

//  => { even: [4, 8, 10], odd: [1, 3] }

// =======================================================

// How was this calculated? Again, lets step through it:

// The categorisingReducer callback is invoked for each array item:

// -- FIRST CALLBACK INVOCATION -- 
// Array Item => 1
// Previous return value => first invocation (so the initialValue is used)
// Callback invocation => categorisingReducer({ even: [], odd: [] }, 1)
// Callback return value => { even: [], odd: [1] }

// -- SECOND CALLBACK INVOCATION --
// Array Item => 3
// Previous return value => { even: [], odd: [1] }
// Callback invocation => categorisingReducer({ even: [], odd: [1] }, 3)
// Callback return value => { even: [], odd: [1, 3] }

// -- THIRD CALLBACK INVOCATION --
// Array Item => 4
// Previous return value => { even: [], odd: [1, 3] }
// Callback invocation => categorisingReducer({ even: [], odd: [1, 3] }, 4)
// Callback return value => { even: [4], odd: [1, 3] }

// -- FOURTH CALLBACK INVOCATION --
// Array Item => 8
// Previous return value => { even: [4], odd: [1, 3] }
// Callback invocation => categorisingReducer({ even: [4], odd: [1, 3] }, 8)
// Callback return value => { even: [4, 8], odd: [1, 3] }

// -- FIFTH (AND LAST) CALLBACK INVOCATION --
// Array Item => 10
// Previous return value => { even: [4, 8], odd: [1, 3] }
// Callback invocation => categorisingReducer({ even: [4, 8], odd: [1, 3] }, 10)
// Callback return value => { even: [4, 8, 10], odd: [1, 3] }

// Final Result: { even: [4, 8, 10], odd: [1, 3] }

Let me know in the comments if this was useful, or if anything about the .reduce() method is still unclear to you!

💖 💪 🙅 🚩
harrison_codes
Harrison Reid

Posted on February 4, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related