Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript JavaScript Array Iteration Methods Combining Array Methods Combining filter() and reduce()

Combining Filter and Reduce - Video confusion

So, I took a break from this section yesterday bc it was becoming a lot. Today I was understanding the first few concepts, then arrived here and I am completely lost.

I suppose what I understand the least, is how in the example, it knew to compare the "highest" value. Same with the sum. If these are variables we are supplying, then how do they know what to do? I understand comparison operators are involved ('>', '+'), I guess I am just missing the link between how they are working here!

2 Answers

Juan Luna Ramirez
Juan Luna Ramirez
9,038 Points

First, reduce allows you to perform some logic for each item in the array with the goal of getting something at the end when it's finished going through each item. This something can be anything, a string, a number, an object, an array of strings, an array of objects, an array of arrays... you get it.

Let's look at the "highest" value example

We can say that the something we want in the end is an object that represents a product with the highest price. So this { name: 'paper towels', price: 6.99 }

next, the first argument to a reduce function, commonly known as a callback function, is the logic that will be use to figure what that something will be at the end, in our case the product object with the highest price. The important part to note here, in regards to your question, is that you are not calling the callback function. You can think of it as the reduce function calling the callback function for you.

// if we were to declare the callback function on its own
function callback (highest, product) {
  if (highest.price > product.price) {
    return highest;
  }
  return product;
}

Basically reduce is saying, "hey, I'm going to go through each item in this array for you and keep track of that something you want in the end... but I need you to tell me how to calculate that something... so, gimme a function I can call to calculate this something"

So you can actually use whatever names you want for the parameters of the callback function. When the reduce function calls it, the first argument will be how that something is coming along as reduce iterates through each item. The value that is returned from the callback function is used by reduce as the first argument again for the next item and so on until there are not more items. The final return value is what you get at the end.

I would suggest looking for more examples online of how reduce works. You may understand someone else's explanation better than this one or the video. I'll post an explanation I wrote in another question which may help but don't feel pressured to understand this from a single explanation.

Juan Luna Ramirez
Juan Luna Ramirez
9,038 Points

Also, filter works in a similar way where you have to pass in a callback function to determine whether to keep the item or not. The return value of that callback function is boolean because you want to say true, keep it, or false don't keep it. Again, you are not calling the function, it is done internally by the filter function.

Another thing, don't get thrown off by chaining array methods. Here is an example without chaining methods.

const productsUnder10 = products.filter(product => product.price < 10) // this result in filtered array

const product = productsUnder10.reduce((highest, product) => {
    if (highest.price > product.price) {
      return highest;
    }
    return product;
  }, { price: 0 }); // result in highest priced from array of products under 10

But it's more convenient to just chain them if you don't need to use the results of productsUnder10 somewhere else in your code.

Juan Luna Ramirez
Juan Luna Ramirez
9,038 Points

reduce allows you to do some work for each item in an array. Then, you get something at the end of doing all that work for every item.

Maybe it would help to think about the output or the return value of running the reduce function. Meaning, what do you want to have at the end of running the reduce function? Is it a brand new array, a number, a string, or an object?

Turns out, you can get anything from the reduce function which makes it very flexible but sometimes difficult to grasp, and I guess explain. OK?! O_o

Let's look at the arguments like you suggest. The reduce function takes 2 arguments:

  1. The function that will do the work for each item in the array. This is often referred to as the callback function or reducer function in case you want to research this some more.
  2. The initial value before we even start looping over the array

so we can say the signature is array.reduce(callback, initialValue).

Initial Value

Let talk about the initial value first since I'm trying to explain this in terms of what you want to get at the end of running reduce.

In practice, I almost always (probably always) use the same data type for the initial value as what I want at the end of running the reduce function. What I mean is:

  • If I want to get a number at the end I'll use a number as the initial value. 8 is the initial value

    const numberOutput = [1, 2, 3].reduce(callback, 8)`
    
  • If I want to get an array at the end I'll use an array as the initial value. [] is the initial value

    const arrayOutput = ['hello', 'hi', 'sup'].reduce(callback, [])`
    
  • If I want an object at the end I'll use an array as the initial value. {} is the initial value

    const objectOutput = ['hello', 'hi', 'sup', 'hello'].reduce(callback, {})`
    

And so on!

This however does not guarantee that the end result will be that same data type as I'll try to show at the end. In fact, the initial value is optional and I would encourage you to research what happens if you don't have it. I don't want to complicate it here for you. I would just recommend using an initial value of the same data type as you want at the end, while you get more familiar with reduce.

Callback Function

Now we'll look at the callback function which allows us to do some work for each item in the array. There is some useful information we might need each time we do this work. These are the parameters of the callback function.

  1. The first thing that would be useful to know is how our final output, the thing we want at the end of running the reduce function is looking thus far. This is often referred to as the accumulator
  2. It is also often useful to know the item of the array we are currently on. This is often referred to as the currentValue

There are 2 more argument but you can research this on your own to keep this simple here.

For simplicity, let's just say the signature is callback(accumulator, currentValue)

ACCUMULATOR

This is the thing we want to get at the end of running the reduce function. So how do you control what this is during each item of the array? During the first item of the array, it will be the initial value we passed as the second argument to reduce. After that, it's whatever we return from the previous callback function.

Let's put it all together in an example: I'm going to add all the 1s in the array to the initialValue 10. So at the end of running reduce I expect to get a number which should be 13

// I'm going to declare the callback function separately so you can,
// hopefully, clearly see the callback parameters and the reduce arguments.

// I just named the parameters "accumulator" and "currentValue" as they are often
// called but you can use whatever names makes sense to you.
const callback = (accumulator, currentValue) => {
  // I'm going to add the current 1 which is saved to currentValue
  // to the current accumulator.
  const updatedNumber = accumulator + currentValue

  // IMPORTANT part: now we are going to return the updatedNumber so that
  // THIS updatedNumber will become the next accumulator when we run this function
  // again for the next item in the array. The last time this run is our final
  // output we are looking for.
  return updatedNumber
}


const numberOutput = [1, 1, 1].reduce(callback, 10)

console.log(numberOutput) // 13

Here are the values of each iteration:

iteration current array value ACCUMULATOR
0 1 10
1 1 11
2 1 12

The return value of the last callback function is the final result, the thing you wanted at the end of running the reduce function, the number 13. Phew!!

Another example. At the end I want an array with all the words in upppercase

// I'm going to put the callback function inline now, but it's all the same
const upcased = ['hi', 'hello', 'hola'].reduce((accumulator, currentValue) => {
  // uppercase the current word
  const wordInUpperCase = currentValue.toUpperCase()

  // add the uppercased word to our acccumulator array
  accumulator.push(wordInUpperCase)

  // important: return the array that now has the newly added word so that
  // it becomes the next accumulator array. The last time this is returned
  // becomes the final output we are looking for.
  return accumulator
}, [])

console.log(upcased) // ["HI", "HELLO", "HOLA"]
iteration current array value ACCUMULATOR
0 'hi' []
1 'hello' ["HI"]
2 'hola' ["HI", "HELLO"]

The return value of the last callback function will be ["HI", "HELLO", "HOLA"], our final output we were looking for.

Let me just show you one final and absolutely obsured example, just to point out that the last return value is our final output. I'm going to repeat the adding of number example but this time I'm going to sabotage the whole thing by returning a string at the very last iteration. Again, this is probably not something you would want to do but that you might actually accidentally do, creating a bug!

// I'm going to use the 3rd parameter which is the index so I know we're
// at the last item, where I'm going to sabotage this thing. Again, research
// the additional parameters.
const callback = (accumulator, currentValue, index) => {
  const isLastIteration = index === 2
  const updatedNumber = accumulator + currentValue

  // it's the last iteration, do something crazy!
  if (isLastIteration) {
    return 'nope!!!'
  }

  return updatedNumber
}


const numberOutput = [1, 1, 1].reduce(callback, 10)

console.log(numberOutput) // "nope!!!"

Here are the values of each iteration:

iteration current array value ACCUMULATOR
0 1 10
1 1 11
2 1 12

Everything was working fine till the end because the return value of the last callback function is just "nope!!". This will be the final result we get. lol!


Yeah, this is one is hard but try out some more example and console out the result to see if you can make it work. I would definitely recommend learning this method though, it's very powerful and useful. Search for other explanations. Hope this is somewhat helpful.