JavaScript Arrays: Reduce
Welcome to the Reduce lesson!
JavaScript arrays' reduce method combines the array's elements to produce a new value. Its most common use is to sum numbers.
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
Sometimes we want to repeatedly apply a function to each element in a list. For example, we might want to sum a list of numbers. This is simple if we know how many numbers there will be, and there are only a few of them.
>
function add(a, b) {return a + b;}add(1, 20);Result:
21
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
add(add(1, 20), 300);Result:
321
We can use a loop to apply this function to every number in an array.
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
const nums = [1, 20, 300, 4000];let sum = 0;nums.forEach(n => {sum = add(sum, n);});sum;Result:
4321
The built-in
.reducemethod simplifies this by running a function on each array element, keeping track of the result. It "reduces" the array to one return value. In this case, that value is the sum of its elements.We'll see
.reducein action first, then discuss how it works. The next example uses.reduceto sum an array of numbers, just like we did above.>
[1, 20, 300, 4000].reduce((sum, current) => sum + current,0);Result:
4321
We pass two arguments to
.reduce. The first is a function that adds a new number to our running sum, returning the new sum. The second argument is 0, the initial value for the sum.The callback function is called once for each number in the array. During each call, it adds the current number to the running sum, then returns that new sum. We can see this in action by instrumenting our function to store all of the sums. (The verb "instrument" means "attach measurement instruments to". It's a great way to learn how unfamiliar systems work!)
>
const intermediateSums = [];[1, 20, 300].reduce((sum, current) => {sum = sum + current;intermediateSums.push(sum);return sum;},0);intermediateSums;Result:
>
const intermediateSums = [];[1, 20, 300, 4000].reduce((sum, current) => {sum = sum + current;intermediateSums.push(sum);return sum;},0);intermediateSums;Result:
[1, 21, 321, 4321]
We can make our
.reducecall shorter by omitting the second argument. In that case, the first array element is used as the sum. This works for many use cases of.reduce, including computing sums.>
[1, 20, 300].reduce((sum, current) => sum + current);Result:
321
When we omit the second argument, our function is never called for array element 0. To sum
[1, 20, 300], this happens:sumis set to element 0 of the array, which is1.callback(1, 20)is called, returning 21 as the newsum.callback(21, 300)is called, returning 321 as the newsum.- There are no more array elements, so
.reducereturns 321.
>
const intermediateSums = [];[1, 20, 300, 4000].reduce((sum, current) => {sum = sum + current;intermediateSums.push(sum);return sum;});intermediateSums;Result:
[21, 321, 4321]
.reduceisn't limited to numbers. In the next example, we have users with names and numericalpointsused to score a game. We reduce the users array to the sum of points for all users.The second argument to the callback function still refers to the current array element. Now this element is a user object, so we name it
userinstead ofcurrent.>
const users = [{name: 'Amir', points: 8},{name: 'Betty', points: 12},{name: 'Cindy', points: 4},];const pointTotal = users.reduce((sum, user) => {return sum + user.points;}, 0);pointTotal;Result:
24
For that example to work, we had to provide the initial value of
0. If we omit the initial value argument,.reducewill try to use the user object at index 0 as the initial value.Adding an object to a number causes the object to be converted into a string,
'[object Object]', which isn't what we want. Things only get worse from there.>
const users = [{name: 'Amir', points: 8},{name: 'Betty', points: 12},{name: 'Cindy', points: 4},];const pointTotal = users.reduce((sum, user) => {return sum + user.points;});pointTotal;Result:
Here's a closer view of what happened to produce that string:
>
const user = {name: 'Amir', points: 8};user + 12 + 4;Result:
'[object Object]124'
We can use
.reduceto emulate the behavior of.join, which works on strings.>
['x', 'y', 'z'].join('a');Result:
'xayaz'
>
['x', 'y', 'z'].reduce((joined, current) => joined + 'a' + current);Result:
'xayaz'
(The first argument of our callback function here is
joined, notsum. It would be strange to call it "sum", since we're no longer adding.)We can write our own general-purpose
.joinusing.reduce. It adds the separator we provide before each element. Note that we skip the second argument to.reduce, so thatseparatorisn't added before the first element>
function join(array, separator) {return array.reduce((joined, current) => joined + separator + current);}join(['x', 'y', 'z'], '_');Result:
'x_y_z'
So far, we've named the callback's first argument
sumandjoined. From now on, we name the callback's first argumentacc.accstands for "accumulator", because it's accumulating the result. Sometimes there are obvious better names, likesumorjoined. But for some examples, there's no obvious good name.If one of the next few examples gives you trouble, try writing it out as a table. At each step in the
.reduce, what are the values ofaccandcurrent? Remember that.reducedoes little more than call the provided function over and over.>
[1, 200, 30].reduce((acc, current) => Math.max(acc, current));Result:
200
>
[1, 200, 30, 4000].reduce((acc, current) => Math.max(acc, current));Result:
4000
>
[true, false, true].reduce((acc, current) => acc && current);Result:
false
>
[true, false, true].reduce((acc, current) => acc || current);Result:
true
>
[false, false, false].reduce((acc, current) => acc || current);Result:
false
>
[[1], [2, 3], [4]].reduce((acc, current) => acc.concat(current));Result:
[1, 2, 3, 4]
What happens when we try to reduce an empty array? If we provide an initial value as the second argument to
.reduce, then.reducewill simply return that value. If the initial value is0, we'll get0out.>
[].reduce((sum, current) => sum + current,0);Result:
0
If we don't provide an initial value,
.reducehas no data to work with, so it errors.>
[].reduce((sum, current) => sum + current,);Result:
When you're using
.reduce, it's important to think about what happens when the array is empty. Otherwise, you may find yourself surprised if you see the error shown above.Reduce is a very abstract method: it can do many different things. The ways to use abstract methods aren't always obvious at first. Once you're comfortable with them, applications will usually show up.
However, it's easy to take
.reducetoo far, so it should be used cautiously. Don't be afraid to use.reducein simple cases, like summing an array of numbers. If you find yourself struggling to read or write a complex.reducecallback, consider using a loop instead. Saving lines is nice when it helps readability, but six easy lines of loop code are better than one confusing.reduceline!