Using Map and Reduce in Functional JavaScript

M. David Green
Share

With all the talk about workflows that support the amazing new features in ECMAScript 6, it’s easy to forget that ECMAScript 5 brought us some great tools to support functional programming in JavaScript which we can use today. Among these are the native map() and reduce() methods on the base JavaScript Array object.

If you’re not using map() and reduce() today, it’s time you started. Most contemporary JavaScript platforms support ECMAScript 5 natively. Mapping and reducing can make your code much cleaner and more easy to read and maintain, and put you on a path toward more elegant functional development.

Performance: A Caveat

Of course, reading and maintaining your code has to be balanced against performance when the situation calls for it. Currently browsers do perform more efficiently using more cumbersome traditional techniques, such as for loops.

My approach is usually to write code for readability and maintainability first, and then optimize for performance if I notice issues in real world situations. Premature optimization is the devil.

It’s also worth considering that using methods such as map() and reduce() may take better advantage of improvements in the JavaScript engine as browsers optimize for them in the future. Unless I’m up against the wall on a performance issue, I prefer to code optimistically, and keep performance tweaks that make my code less attractive in my back pocket in case I need them.

Using Map

Mapping is a fundamental functional programming technique for operating on all of the elements in an array and producing another array of the same length with transformed contents.

To make that a little bit more concrete, let’s come up with a simple use case. For example, imagine that you have an array of words, and you need to transform that into an array that contains the length of each word. (I know, that’s not the sort of complex rocket science you often need to do for your sophisticated application, but understanding how it works in a simple case like this will help you apply it in cases where it can add real value to your code).

You probably already know how to do what I just described using a for loop on the array. It might look something like this:

var animals = ["cat","dog","fish"];
var lengths = [];
var item;
var count;
var loops = animals.length;
for (count = 0; count < loops; count++){
  item = animals[count];
  lengths.push(item.length);
}
console.log(lengths); //[3, 3, 4]

All we did was define a few variables: an array called animals that contained our words, an empty array called lengths that will contain the output of our operation, and a variable called item to temporarily store each of the items that we were going to be manipulating within each loop of the array. We set up a for loop with a temporary internal count variable and a loops variable to optimize our for loop. Then we iterated through each of the items up to the length of the animals array. For each one we calculated the length, and pushed that onto the lengths array.

Note: Arguably we could have done this a little more concisely without the item variable by pushing the length of animals[count] directly to the lengths array without an intervening assignment. That would have saved us a little bit of code, but it would also make things less readable, even for this very simple example. Similarly, to make this more performant but a little less straightforward, we could have used the known length of the animals array to initialize our lengths array as a new Array(animals.length), and then inserted items by index instead of using push. It all depends on how you’re going to use the code in the real world.

There’s nothing technically wrong with this approach. It should work in any standard JavaScript engine, and it will get the job done. But once you know how to use map(), doing it this way just looks clunky.

Let me show you how we might approach this using map():

var animals = ["cat","dog","fish"];
var lengths = animals.map(function(animal) {
  return animal.length;
});
console.log(lengths); //[3, 3, 4]

In this case, once again we started with a variable for our animals array of animal types. However, the only other variable we declared was lengths, and we assigned its value directly to the result of mapping an anonymous in-line function onto each element of the animals array. That anonymous function performed an operation on each animal, returning the length. As a result, lengths became an array of the same length as the original animals array, containing the length of each word.

A few things to notice about this approach. First of all, it’s much shorter than the original. Second, we had to declare far fewer variables. Fewer variables means less noise in the global namespace, and less opportunities for collisions if other parts of the same code use variables with the same names. Additionally, none of our variables ever had to change their values from beginning to end. As you get deeper into functional programming, you’re going to appreciate the graceful power of using constants and immutable variables, and it’s never too early to start.

Another advantage of this approach is that we have the opportunity to improve its versatility by splitting out a named function, producing cleaner code in the process. Anonymous in-line functions can look messy, and make it harder to reuse code. We could have defined a named getLength() function and used it in context this way:

var animals = ["cat","dog","fish"];
function getLength(word) {
  return word.length;
}
console.log(animals.map(getLength)); //[3, 3, 4]

See how clean that looks? Just making mapping part of your toolkit can take your code to a whole new functional level.

What’s a Functor?

As a point of interest, by adding mapping to the array object, ECMAScript 5 turned the basic array type into a full functor, making functional programming even more accessible to all of us.

According to classical functional programming definitions, a functor meets three criteria:

  1. It holds a set of values
  2. It implements a map function to operate on each element
  3. Its map function returns a functor of the same size

That’s one to toss around at your next JavaScript Meetup.

If you’d like to learn more about functors, check out this great video by Mattias Petter Johansson.

Using Reduce

The reduce() method is also new in ECMAScript 5, and it’s similar to map(), except that instead of producing another functor, reduce() produces a single result that may be of any type. For example, imagine that you wanted to get the sum of the lengths of all of the words in our animals array as a number. You might start by doing something like this:

var animals = ["cat","dog","fish"];
var total = 0;
var item;
for (var count = 0, loops = animals.length; count < loops; count++){
  item = animals[count];
  total += item.length;
}
console.log(total); //10

After we define our initial array, we create a variable total for the running total, set initially to zero. We also create a variable item to hold each iteration of the animals array as it goes through the for loop, and a variable count for the loop counter, as well as a loops variable to optimize our iterations. Then we run a for loop to iterate through all of the words in the animals array, assigning each one to the item variable. Finally we add the length of each item to our total.

Again, there’s nothing technically wrong with this approach. We start with an array, and we end up with a result. But with the reduce() method we can make this much more straightforward:

var animals = ["cat","dog","fish"];
var total = animals.reduce(function(sum, word) {
  return sum + word.length;
}, 0);
console.log(total);

What’s happening here is that we’re defining a new variable, total, and assigning it the result of reducing the animals array using two parameters: an anonymous in-line function, and an initial running total value of zero. Reducing goes through each item in an array, performs a function on that item, and adds it to a running total that is passed to the next iteration. Here our in-line function takes two parameters: the running sum, and the word that is currently being processed from the array. The function adds the current value of total to the length of the current word.

Notice that we’re setting the second argument of reduce() to zero, and this establishes that total will contain a number. Without that second argument the reduce method will still work, but the result won’t necessarily be what you would expect. (Give it a try and see if you can derive the logic JavaScript uses when the running total is left off.)

That may look a little bit more complicated than it needs to because of the integrated definition of an in-line function while invoking the reduce() method. Let’s do that again, but let’s define a named function first, instead of using an anonymous in-line function:

var animals = ["cat","dog","fish"];
var addLength = function(sum, word) {
  return sum + word.length;
};
var total = animals.reduce(addLength, 0);
console.log(total);

This is a little longer, but longer isn’t always a bad thing. Seeing it this way should make it a little bit clearer what’s happening with the reduce method.

The reduce() method takes two parameters: a function to apply for each element in the array, and an initial value to use for the running total. In this case we’re passing the name of a new function called addLength and the initial value of zero for the running total. We’ve created the addLength() function so that it also takes two parameters: a running sum, and a string to process.

Conclusion

Getting used to using map() and reduce() on a regular basis will give you alternatives to make your code cleaner, more versatile, and more maintainable, and pave your way toward using more functional JavaScript techniques.

The map() and reduce() methods are only two of the new methods that were added to ECMAScript 5. In all likelihood, the improvements in code quality and developer satisfaction you’ll see from using them today will far outweigh any temporary impact on performance. Develop with functional techniques, and measure the impact in the real world before deciding whether map() and reduce() are right for your application.

This article was peer reviewed by Panayiotis Velisarakos, Tim Severien and Dan Prince. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!