When you first get started learning about functional programming in JavaScript, there are a few functions you grasp pretty easily. Array.prototype.filter
is very intuitive, especially if you have worked with a language like SQL which is all about filtering out data. Array.prototype.map
is a bit more complex, but you can figure it out pretty quickly if you’re given a few examples.
Then there is Array.prototype.reduce
.
Also known as fold
, inject
, aggregate
, accumulate
, or compress
in languages like Haskell, Clojure, and Lisp, reduce is a weird function. For the longest time, I struggled to really grasp how the reduce function works. I think the reason why is because reduce tries to do a few things at once. Whereas filter removes items that fail your test, and map modifies your data into a new dataset, reduce kind of does a mishmash of a few things.
The examples in places like MDN are overly simplistic - they basically show reduce as a way of doing addition by folding one result onto the next. Which is fine, but stumps people the moment you aren’t working with numbers. Here’s how it usually looks:
// Summation of numbers in an array
[1, 2, 3].reduce((sum, n) => sum + n);
This is a perfectly valid example, but you might be wondering: How do you reduce an array of strings? Why would you reduce an array of strings? What are some real-world examples of using reduce to help you in your day-to-day work? We’re going to attempt to answer those in this article so you can master the reduce function and finally feel comfortable using it.
Definition and breakdown
Reduce’s job is to take a collection of items and reduce them down to one singular value. The type signature of this function looks like this:
reduce: (callback: function, initialValue?: any) => any
callback: (accumulator: any, currentValue: any, currentIndex?: number, array?: array) => any
We are using TypeScript’s function type annotation syntax for this example. In short, it just helps decode the inputs and the outputs. So our function takes in two arguments we’ve called callback
and initialValue
. What are those arguments?
callback
argument and its type signature
callback
is a function
that takes four arguments itself: accumulator
, currentValue
, currentIndex
, and array
.
accumulator
is of the type of thing you’re trying to make your outputValue
. So if you’re reducing an array of number
s through addition, the accumulator
is a number
. It is accumulating, or growing, in size not by the length of the array but by the “growth” of the eventual output value. In fact, there is an inverted relationship by how the accumulator grows against how the array is reduced. This object is definitely the most confusing, so if this still doesn’t make sense, we’ll break this down in the later examples.
currentValue
is an item in the array you’re trying to reduce. As you iterate over the array, your currentValue
changes as you move from the first to the last element in that array.
currentIndex
is an optional number
argument that coincides with the currentValue
by expressing that value’s index. So as you are iterating over your initial array, the currentIndex
will grow from 0
up to the n-1
number of elements in your array.
array
is an optional Array
argument that is the entire input array you provided to the reduce
function. This might seem like an odd thing to pass in but the key here is that the input array is not modified. Functional methods obey immutability, meaning they do not modify the array object they are provided. Therefore, regardless of what you do in your reduce
method, you always have access to your input array because it is never changed throughout the execution of reduce
.
initialValue
argument
initialValue
is an optional argument that provides the initial value for accumulator
to begin reducing. For example, if you wanted to reduce [1,2,3]
into the summation of those values, you’d want the initialValue
to be set to 0
to ensure you are adding against the same type (a number
) and that this value does not pollute the values that are being manipulated by the array. If you do not set this argument, the accumulator
will be set to the first value of the array. In the above example, that would be 1
.
outputValue
return object
outputValue
is the final form of the accumulator
object once the entire array
has been iterated. Unlike most array functional methods, which return arrays, this will return the type that you have built throughout the iteration. This is not necessarily the same type as the values within your input array, as we will see below.
Examples
Now that we know what goes in and out of the reduce
function, let’s dive into a few examples, step-by-step, in increasing difficulty.
Easy - sum elements of an array
As we mentioned earlier, the ever-present example of reducing arrays is to sum a list of numbers through addition. It can be succinctly expressed like this:
[1, 2, 3].reduce((sum, n) => sum + n);
The easiest way to see how the iteration is working is to throw log statements at each step for our two input arguments:
[1,2,3].reduce((a, c) => {
console.log(`accumulator: ${a}`);
console.log(`currentValue: ${c}`);
return a + c;
});
// FIRST ITERATION
// accumulator: 1
// currentValue: 2
// SECOND ITERATION
// accumulator: 3
// currentValue: 3
// FINAL ITERATION (AS RETURN VALUE)
// 6
On the first iteration, we grab the first element in our array, 1
, and set that as our accumulator
value. We then add our currentValue
, which is the second element in the array, 2
, onto our accumulator
, because that is the logic we have decided on for our callback
function. 1+2 equals 3, so 3
therefore becomes our new accumulator
value in our second iteration. Since no initialValue
is set, our second iteration is actually operating on the third and final element, 3
, which is added on to our new accumulator
value of 3
. 3+3 equals 6, and with no elements left to iterate on in our array, we return the final accumulator
value of 6
.
Easy! Now, what happens if we operate on another kind of type, like a string
?
Medium - simple string builder
One of the most common things I see in JavaScript code involves building strings like this:
const myBuddies = ['James', 'Darien', 'Eric'];
let greeting = 'Greetings friends! ';
for (let i = 0, l = myBuddies.length; i < l; i++) {
greeting += myBuddies[i] + ' is my buddy. ';
}
greeting += 'These are all of my buddies!';
return greeting;
You start with an array and an empty string. You build the string up from the array elements, maybe adding in some additional information, and then you return the new string, fully formed and populated. Now look what happens when I rearrange and rename some variables, do you see the connection?
const array = [some, array, values, ...rest];
const initialValue = 'Greetings friends! ';
let accumulator = typeof initialValue === "undefined" ?
initialValue :
array[0];
const manipulation = (a, b) => a.concat(b);
let i = (typeof initialValue === "undefined" ? 0 : 1);
let l = arr.length;
for (i, l; i < l: i++) {
accumulator = manipulation(accumulator, array[i]);
}
return accumulator;
This iterative pattern is the reduce
function. The callback
within reduce
is simply some function of manipulation
of data, iterated across an array
and saved into some built value called an accumulator
! The difference is, our last example is 8 lines of code, and to make this in reduce
, we can express it in a very clean and terse way, with zero local variables needed:
['James', 'Darien', 'Eric']
.reduce((paragraph, name) => {
return `${paragraph}${name} is my buddy. `;
}, 'Greetings friends! ')
.concat('These are all of my buddies!');
This is pretty neat. We were able to remove all local variables and reduce the line count by more than half. We are also able to chain functions together to continue to tack on unique statements at the end of our string builder.
While this instance is a bit more involved and doesn’t simply manipulate numbers, at the end of the day, this is still a form of summation, which may seem a bit too simplistic and similar to the first example. Lucky for you, we’ve got one last example that should be a bit more advanced and is not simply a form of addition.
Hard - flatten array to dictionary
Our last example is not going to simply be some form of summation. Instead, we’re going to build a dictionary (also known as a hash table, which is simply an Object
in JavaScript) from an array of arrays.
We’re going to make the first value in our sub-array be the keys, and the rest of our values in our sub-array be the values. If there are hash collisions (meaning multiple sub-arrays with the same value in the first element), we simply add the remaining sub-array items onto the existing key/value pairing.
How are we possibly going to do this with reduce
?
We know our initial value is {}
. We aren’t doing a summation (or concatenation if we’re dealing with strings), but we are still trying to manipulate our output value, which we know to be an Object
as a hash table. How can we add things onto a hash table in JavaScript? Square bracket notation! That should be enough to get us what we want:
const arrayOfArrays = [
['a', 'ace', 'apple', 'axe'],
['b', 'baseball', 'boy', 'bye'],
['c', 'cat', 'cold'],
['a', 'azure'],
['a', 'azure']
];
const dictionary = arrayOfArrays.reduce((dict, page) => {
const [letter, ...words] = page;
dict[letter] = dict.hasOwnProperty(letter) ?
dict[letter].concat(
words.filter(word => !dict[letter].includes(word))
) :
words;
return dict;
}, {});
dictionary['a'][0];
// 'ace'
dictionary['a'][dictionary['a'].length - 1];
// 'azure'
dictionary['a'].filter(word => word === 'azure').length
// 1
Lots to unpack here. To start, we created our multi-dimensional array called arrayOfArrays
. Each row in the array starts with the letter of our dictionary. That letter will represent the various pages in our dictionary. You could do this without declaring the local variable, but for the sake of readability within this blog, I chose to separate them.
Next, we construct our actual dictionary. We start with our initial Object
, a simple empty {}
hash. Our callback
represents two things, the dict
dictionary object that we are constructing, and the page
, which represents a row in our arrayOfArrays
. That row has two kinds of strings: the letter representing what class of words we’re on, and those words listed on that page.
In languages like Haskell, lists come with functions called head
and tail
(also known as car
and cdr
in older languages like Lisp). In JavaScript, these don’t come out of the box, but thankfully in ES6 we can quickly grab these through the destructuring pattern [first, ...rest] = array
. We do exactly this to grab our letter
and corresponding words
.
Next, we have to build our dictionary. There are two major considerations here: new pages and existing pages.
If we have a new page, we are dealing with the simpler case. All we have to do is populate the page (whose key is letter
) with our words
.
If we are dealing with an existing page, we want to add onto that existing page using concat
. But we can’t simply concatenate our list of words
. What if we’ve already added a word to our dictionary? That’s where our functional cousin filter
comes in to filter out words that are already included on this page. The filter will return only novel words
, removing duplicates along the way.
Finally, we test out our new dictionary
variable to prove our dictionary is complete, can handle adding words late to our prepopulated keys, and will gracefully handle duplicates.
Final thoughts
Congratulations! You now know how to reduce an array down to a composite value. You’ve not only mastered the reduce function in JavaScript but in every other language.
As we mentioned in the beginning, the concepts behind reduce
are the same across languages like Clojure and Haskell, but with different names like fold
or inject
. You can also now reason about variants on reduce
, such as foldr
(i.e. fold right, which is the same as reduce
), or foldl
(i.e. fold left, similar to reduce
except the iteration happens from the back to the front of the array).
If you’re still confused, I’ve failed you. But fear not; find me on Twitter and I’d be happy to provide even more examples. I will not rest until everyone reading this knows the power of reduce
!
Get the FREE UI crash course
Sign up for our newsletter and receive a free UI crash course to help you build beautiful applications without needing a design background. Just enter your email below and you'll get a download link instantly.