loading...
Cover image for 3 Simple Snippets to Help You Understand Array .reduce()! πŸ₯³

3 Simple Snippets to Help You Understand Array .reduce()! πŸ₯³

harrison_codes profile image Harrison Reid ・5 min read

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!

Posted on Feb 4 by:

harrison_codes profile

Harrison Reid

@harrison_codes

My side projects haunt my dreams.

Discussion

markdown guide
 

Nice article!!!
I have a doubt about the second example , when you use curretMax and numberToCheck, I don't understand where it gets these parameters and put inside the block.
Then I don't understand how work callback into reduce works.
Thanks!!!

 

Thanks! Lets see if I can explain this...

The const findLargest is a function that accepts two arguments. I've called these arguments currentMax and numberToCheck, but those names only really make sense in the context of that function being used as a callback in .reduce(). We could equally call those arguments firstNumber and secondNumber, so lets do that...

const findLarger = (firstNumber, secondNumber) => {
  if (secondNumber > firstNumber) {
    return secondNumber;
  }
  return firstNumber;
}

Looking at this function now, you'll see it just receives two numbers, and returns the larger of the two.

We then pass this function as the callback to .reduce():

const numbers = [ 10, 40, 4, 50, 101 ]
const largest = numbers.reduce(findLarger) 

From here, .reduce() iterates over the array (but skips the first element, as we haven't provided an initial value for reduce to use, so it uses the first array item as this initial value).

For each subsequent array item, reduce will call the findLarger() function, and will pass in two arguments. The first will be the return value from the previous call to findLarger(), or the initial value if there were no previous calls. The second will be the current array item.

So for each array item, reduce calls:

// This isn't real code, just trying to annotate where the arguments are coming from...
findLarger(<previousReturnedValue>, <currentArrayItem>)` 

Since findLarger() returns the larger of two numbers, and this returned number is then passed into the next findLarger() call, and so on, the last number that is returned once we've iterated over the full array should be the largest.

Hopefully this helps! And thanks for reading πŸ˜ƒ

 

Reduce is a tricky one indeed. I like the style of this article. Short, concise and practical. Good job!!.

 
 
 
 

Finally! I understand reduce :)

 

Thank you very much for this concise and practical article :D