E Pluribus Unum
A Compact Introduction to Array.prototype.reduce
When I started learning the very basics of functional programming as it pertained to JavaScript arrays, most of it seemed very clear and straightforward to me. Array.prototype.map()
takes an array, does something with each element, and returns a new array. Makes sense. Array.prototype.filter()
takes an array and returns another array full of elements that meet some specified requirements. No surprise there! Array.prototype.forEach()
is self-explanatory, do something for every element in the given array. Easy!
And then I came upon Array.prototype.reduce()
. I wasn’t entirely sure what this function was doing, so I read up on it from a variety of sources. And the examples I was able to find showed me how I could use it to add up an array of numbers.
“Cool”, I remember thinking, “but I can just iterate over an array using a for
loop and keep track of the sum in a variable”. But after reading a little more about this “let’s add numbers into a bigger number” method, I came to understand that this method was not just for adding numbers; in fact, this use case is a misleading trivialization of the potential of the reduce
method!
Array.prototype.reduce()
executes a defined reducer function on each element in the array, resulting in a single output value. And that value is not restricted to being any particular data type! It can be a number, as demonstrated above, but it can also be a string, an object, another array, or whatever it is needed to be!
This makes the reduce
method an incredibly powerful tool. Let’s take a look first at the basic syntax of the reduce method.
At its simplest, it will look something like this:
arr.reduce(callbackFunction);
Where callbackFunction
is a function that executes on each element of the array. This function takes up to four arguments, of which only the first two are necessary:
accumulator
: (required) A variable representing the accumulation of the callback’s return values. It is by default the accumulated value returned in the last invocation of the callback.currentValue
: (required) The current element being processed.currentIndex
: The index of the current element being processed.array
: The array that the function was called on.
Typically, only the first two are used, as in the sum example up above. To simplify, the accumulator holds the value of all operations done, and is what is returned from the function itself. With execution on each element, something is done with that element and the accumulator is modified accordingly. In the case of adding numbers together, an element is taken and added to the current accumulation, the sum of which operation is then set as the accumulator for the processing of the next element.
The way that reduce
works under the hood is conceptually simple. If we walk through a simple case of adding an array of numbers into a single sum, it makes understanding this function effortless.
Say we have an array of [1, 2, 3, 4, 5]
. We’ll want to write a function that looks like this:
[1, 2, 3, 4, 5].reduce((sum, current) => {
return sum + current;
}, 0);
I’ve set the initial value of sum to be 0 via the second argument. We’ll talk more about that in a bit! Since we have 5 elements in our array, our callback function will run 5 times.
- On the first call, our sum is 0, and our current element being processed is 1. The function will return the sum plus the value of the current element, which in this case is 0 +1 = 1. 1 is set as the sum, and we get ready to process the next element.
- On the second call, our sum is 1, and our current element is 2. We now have a sum of 3, and will go on to the next element.
- On the third call, our sum is 3, and our current element is 3, so we have a sum of 6, and are on to the next one!
- On the fourth call, our sum is 6 and the current element is 4, which gives us 10.
- On the fifth call, our sum is 10, and the current element is 5, which gives us a total of 15. As there are no more elements to process, the value of 15 is returned, and our array has successfully been reduced into a single value.
But as I said before, adding numbers in an array does nothing to show the real power of which this function is capable.
The reduce
function can (and often does) take another argument after the callback function; this argument is referred to as the “initial value”. For example, let’s say I wanted to use reduce to take an array of letters and return a word made up of those letters.
After specifying the callback function, I’m placing an empty string as the second argument. This tells the reduce function “hey, I want you to use this as the starting accumulator”, and, as specified by the callback function, it will add the first letter to an empty string.
“But will the reduce function work if I don’t specify an initial value?” Well, the answer in this case (and the summing numbers case I mentioned earlier) is, in fact, yes. The reason for this is that when no initial value is provided, the method automatically takes the first element in the array and sets that as the initial value. However, let’s say I have an array of numbers and I want to convert them to a string. Easily doable with reduce
!
The reduce
function is extremely powerful, and capable of doing far more than adding values together. In fact, just about every other array method can be written with it!
It is important to note that the above implementation of map()
requires the initial value to be defined as an array! If I did not specify an initial value, my function would have taken the first element in the array as its first accumulator. While often that is all well and good, in this case it would set the accumulator as a number, which does not bode well for the use of the .concat()
method! This may seem obvious, but it is important to be aware of exactly what you want returned from the reduce method.
Let’s try implementing filter()
as well:
Another important point to note is that I have to return something from processing each element, even if nothing was done with that element! In this example, I need to be returning an array each time the function is called, or I lose my accumulation! So in the logic of my implementation, if the current element fulfills the conditions specified by the callback function, I simply add it to the array and return the array; if it doesn’t fulfill the conditions and must be filtered out, then the array is returned without the element appended to it, and the next element is processed.
That about wraps up the basics of Array.prototype.reduce()
, but this is far from an exhaustive list of the multitude of operations of which this seemingly magical method is capable. See if you can think of ways to use it in your code! Remember, given a number of items, all reduce
does is make them into one final result of your choosing; how it does so is up to you!