Reduce your fears about Array.reduce()

Dave Lunny
10 min readMay 22, 2017

--

Source: https://youtu.be/rFH5hredJl0?t=2m45s

If you spend most of your day working with JavaScript, then you’re probably familiar with arrays and their built-in methods such as map, filter, and forEach. One method that you might not be as familiar with (especially if you are somewhat new to JavaScript) is the reduce method, which of all the array methods is my favourite.

But it was not always this way for me; I went far longer than I would care to admit without learning how to properly use reduce. It wasn’t until Redux and the rest of the functional programming train came roaring through Javascriptland that I finally sat down and wrapped my head around it. And oh man is it ever powerful, which is why I use it so frequently now without even thinking about it. Let me show you why I like it so much.

Thinking in Arrays

Arrays are a great way to model data, primarily because they are iterable (iteratable…? iterateable…?). This means you can use methods such as reduce (along with map, filter, or with your classic for loops) to move through each item in the array and perform some operation on each item.

And if you look around, UI is often displayed in lists, and there are heaps of apps out there that do this. Think about your Twitter or Facebook feeds, which are essentially just (never-ending) arrays of objects. To-do list apps are lists as well. Or think about a chat app: the feed of messages could be expressed as an array, and so can your list of conversations.

Common Patterns

When you start representing your application data as lists of objects, you will often need to either get some data from each item, or perhaps express your data in a slightly modified way. Let’s say we have an array of users, and it looks something like this:

const users = [
{
firstName: 'Bob',
lastName: 'Doe',
age: 37,
}, {
firstName: 'Rita',
lastName: 'Smith',
age: 21,
}, {
firstName: 'Rick',
lastName: 'Fish',
age: 28,
}, {
firstName: 'Betty',
lastName: 'Bird',
age: 44,
}, {
firstName: 'Joe',
lastName: 'Grover',
age: 22,
}, {
firstName: 'Jill',
lastName: 'Pill',
age: 19,
}, {
firstName: 'Sam',
lastName: 'Smith',
age: 22,
}
// Let's just pretend the list goes on...
// Also I am terrible at making up names...
// ...I mean come on, two of those surnames are animals smh...
];

For some reason or another, we need a new array of all users’ full names (their first name, plus a space, plus their last name) but only if they are in their twenties, and only if their full name is 10 characters or longer. Don’t ask why we’d ever need such an odd subset of the data, the product manager asked for it and we are happy to oblige. 👍

A typical functional programming approach to this problem might look something like this:

const twentySomethingsLongFullNames = users

// First we filter only the users who are in their twenties:
.filter(user => user.age >= 20 && user.age < 30)

// Concat first and last names:
.map(user => `${user.firstName} ${user.lastName}`)

// Now remove any names that are 9 or less characters
.filter(fullName => fullName.length >= 10);

Maybe you want to test each of these functions independently, so let’s make this even more readable by breaking each piece off into it’s own named function, like so:

const isInTwenties = user => user.age >= 20 && user.age < 30;
const makeFullName = user => `${user.firstName} ${user.lastName}`;
const isAtLeastTenChars = fullName => fullName.length >= 10;

const twentySomethingsLongFullNames = users
.filter(isInTwenties)
.map(makeFullName)
.filter(isAtLeastTenChars);

This is a beautiful way to express this, and for smaller arrays like the example users set, this is absolutely the way I would recommend you handle a problem such as this. Each of those functions can be tested independently, which is sweet.👍 However, it’s important to note that this is not the only way to accomplish this, and when you are dealing with problems like this at scale, it might be beneficial to consider other options.

This is where my homeboy reduce is gonna come thru for us…

☝️ See, this guy gets it…

Reducing (😉) the number of iterations

In the original solution provided above, we iterated over the array a total of 3 times (or rather, a modified version of the array that is returned from each of the 3 methods). Now for most cases, this is fine, because generally we aren’t dealing with vast sets of data, so if you throw a couple of iterations in there it’s probably not going to take that long to run through them. Plus remember that for each filter in that chain, a smaller array would presumably be returned, so it’s not like you’re iterating over the entire length of the original array every time. But still, this is not ideal.

Let’s say our app(s) is/are super successful and we are dealing with data on the scale of like Google/Facebook/Hooli. In situations like this, we want to reduce (pun intended) the amount of computational overhead, and looping through an array three times when we could do it in only a single pass might be a significant performance win for us.

Let’s give it a shot, and in this example we are using reduce, plus the same three named functions that we wrote above (because we’ve also already written tests for them and we aren’t down to re-write or remove those):

const isInTwenties = user => user.age >= 20 && user.age < 30;
const makeFullName = user => `${user.firstName} ${user.lastName}`;
const isAtLeastTenChars = fullName => fullName.length >= 10;

const twentySomethingsLongFullNames = users.reduce(
// First argument is our reducer/callback function:
(accumulator, user) => {
const fullName = makeFullName(user);
if (isInTwenties(user) && isAtLeastTenChars(fullName)) {
accumulator.push(fullName);
}
// Always return the accumulator (for the next iteration)
return accumulator
},
// The 2nd argument (optional) is the initial value:
[]
);

Let’s break down what’s happening. The reduce method accepts two arguments, the first is our reducer/callback function, and the second (totally optional) one is the initial value.

Then the reducer function itself accepts a few arguments, the first is the accumulator and the second is the item in the array (there’s a third for the current index in the array, and a fourth that’s a reference to the OG array).

The accumulator is basically what you are building up as you move through the array. The initial value argument is (if you provide one) used as the value of the accumulator when our reducer function is run on the first item in the array. If you do not provide an initial value, then reduce will actually start at index 1 (so the second item in the array), where the accumulator will be the first item in the array.

Now, what I like about reduce is that it’s name basically describes exactly what it does — you want to reduce an array into whatever data you want to get out of that array. In our case, the end result that we want is a new array (of full names of users), but it doesn’t have to be another array that you reduce to. You could reduce to a new object, or a primitive type, such as a boolean or a number, or really any type of value you want.

What’s the benefit of reducing iterations?

In case you were curious about what I mentioned earlier about performance, I tested this by generating a huge array of users (100,000 items). I then did some crude console.timeing to check which was faster, the three methods approach or the single pass with reduce. And as expected, the single pass version is much faster (like more than 3 times faster):

🚀🚀🚀

Again, the single pass version isn’t necessarily “better”, and I would argue that the filter-map-filter version is much more readable. But if you’re the type of developer who gets joy out of writing code that is as performant possible, or if you notice that your app is slower at scale, then maybe it’s worth considering reducing iterations over large data sets.

Neat, but show me another example

Maybe you don’t work with that scale of data, and maybe you don’t see the power of reduce yet. Let’s quickly step through how it works again by implementing our own (crude) version of Array.find using reduce.

If you haven’t used Array.find yet, it iterates over your array, calling a function on each item, and if that function returns true, we return that item from the array. Pretty handy, but since it’s a newer feature, browser support is lacking, so let’s use reduce instead! First step looks like this:

const fruits = [
{ name: 'apples', quantity: 2 },
{ name: 'bananas', quantity: 0 },
{ name: 'cherries', quantity: 5 }
];
const thisShitIsBananas = fruits.reduce((accumulator, fruit) => {
return accumulator
});

Rule number one is to ALWAYS return your accumulator (or at least return something). Whatever you return from your reducer callback gets used as the accumulator when called on the next item in the array, so when you get to the final item of the array, if nothing is returned you get undefined 👎

Next, we want to modify the accumulator (or better yet just return the item that we want). We can do this like so:

const thisShitIsBananas = fruits.reduce((accumulator, fruit) => {
if (fruit.name === 'bananas') return fruit;
return accumulator
});

Now when we get to 'bananas' in our array, we return that item, which becomes the new accumulator, and for all the subsequent iterations we just return that accumulator until the end when, where it is returned as the final result. But that’s lots of hardcoding, so personally I’d clean it up like this:

//  arrayFind accepts an array and returns a function
// the returned function accepts the finder function
const arrayFind = arr => fn => arr.reduce((acc, item, index) => {
// We pass the finder function the item and the index
if (fn(item, index)) return item;
return acc;
});
// Creates a finder function for just our fruits
const fruitFinder = arrayFind(fruits);
// Now we can pass a simple finder function to fruitFinder
// This is what the `fn` refers to above:
const thisShitIsBananas = fruitFinder(fruit => fruit.name === 'bananas');

That way our arrayFind just takes an array and it’s returned function works much like the actual Array.find in that it simply takes a finder function. 👌

Note that I did say this is a crude version of Array.find — the actual version will return the first item in the array that matches, whereas what we just wrote up there ☝️ would actually return the last item in the array that matches (so in our case, if there were more than one 'bananas', the last one would be returned). Not perfect, but you can fix it for homework. 😛

Final example: reduce an Array to a simple String

One last example. Let’s use the sample users dataset that was used in the first example above, and this time our product manager wants a string of all users (regardless of age or length of name), with each person’s full name on a new line (maybe to write to an .md file or something, who knows).

I will show you a naive implementation of this first, then below I will show you how I would accomplish this using reduce, which is a lot easier:

/*  Naive implementation  */
let everyonesName = '';
users.forEach(user => {
everyonesName += `${user.firstName} ${user.lastName}\n`;
});
/* Better implementation */
const everyonesName = users.map(
user => `${user.firstName} ${user.lastName}\n`
).join('');
/* Best implementation */
const everyonesName = users.reduce(
(acc, user) => `${acc}${user.firstName} ${user.lastName}\n`,
''
);

See that in the first implementation, we are constantly mutating our everyonesName variable. No good. The second implementation isn’t bad, but the map simply returns another array that the join will then have to iterate through to create the final string.

Contrast these with the final implementation with reduce, where we are iterating only once, and returning a new string which concats the full name of the current user in the array with the existing string of names (the accumulator), with the final result string being returned out of our reduce the last time the function runs. 🎉

Note that our initial value here is an empty string, because that’s what we want the first acc to be empty when we concat it with the first name in the array. If we were building, say, a markdown file out of this, and wanted a heading that said “User list” or something, we could easily use that as the initial value instead, and it would be prepended to the list of users. Pretty dang handy, not gonna lie.

I hope you are now a little bit more comfortable with reduce, and even if you don’t find yourself reaching for it as much as the other array methods, it’s still an important tool for you to have at your disposal. If you want to practice getting a better handle on it, I recommend trying to implement some of the other array methods using reduce, just for fun.

Thanks for reading, and feel free to hmu on Twitter with any questions or corrections, or with other suggestions about good use cases for reduce!

I also want to give big ups to this Egghead course, which was super helpful for learnin’ me some reducin’, so thanks @mykola for creating it.

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!

--

--

Dave Lunny

Developer at FOSSA • Not afraid to use emojis in commit messages