Breaking the Loop: How to use higher order functions to process arrays in JavaScript
There’s a better way to iterate over your Arrays than using a ‘for’ loop or a ‘while’ loop. I’ll show you how to use ‘forEach’, ‘map’, and ‘reduce’, for cleaner, more modular code.
Somewhere around the second week of your first JavaScript course, you were no doubt introduced to the Array. Arrays are pretty neat! Instead of having to work with values one at a time, you had a way to bundle your values up into a nice data structure that you could refer to with one variable name. You could add multiple values to the Array and easily use them all throughout your code just by referring to their index. And you no doubt used everyone’s favorite variable ‘i’ with a ‘for’ loop or a ‘while’ loop to do so.
But there’s a problem with this approach. Pretty quickly, you probably noticed you were copying-and-pasting loop code; a telltale sign that your code may not have been DRY. Beyond the actual loop control statement, you didn’t have an easy way to reuse logic inside the loop. Using higher order functions to process your Arrays can fix both issues. Let’s look at some common patterns you may have come across and then utilize higher order functions to refactor them.
Perhaps you needed to take each value in the Array and send it off to another function as an argument, like logging each value to the console. A ‘for’ loop would get the job done:
// Call console.log with each value in the Array
// as an argumentvar names = ['Sonny', 'Joel', 'Isabelle', 'Henry'];for (var i = 0; i < names.length; i++) {
console.log('Hello '+ names[i]);
}// Logs:
// "Hello Sonny"
// "Hello Joel"
// "Hello Isabelle"
// "Hello Henry"
Another common scenario you eventually ran across is needing perform some operation on each value in the Array and save the result in a new Array. A ‘for’ loop would have worked here too, but you could’ve also used a ‘while’ loop, incrementing ‘i’ along the way:
// Double each value in an Array; produce a
// new Array with the same length as the originalvar numbers = [0, 1, 2, 3];var i = 0;
var doubles = [];while (i < numbers.length) {
doubles[i] = numbers[i] * 2;
i++;
}// doubles is: [0, 2, 4, 6]
You may have also found yourself in a situation where you needed to use all the data contained in the Array to find a single result. You may have wanted to find the sum of all the values in an Array of numbers, or you may have wanted to find the longest word in an Array of Strings:
// Find the longest word in an Array of Stringsvar words = ['space', 'moon', 'crater'];
var longestWord = '';for (var i = 0; i < words.length; i++) {
if (words[i].length > longestWord.length) {
longestWord = words[i];
}
}// longestWord is: 'crater'
Typing out that ‘for’ loop over and over was repetitive and made your code hard to reuse. You need a better solution.
forEach, map, and reduce to the rescue
Luckily for you and I, JavaScript Arrays come pre-packaged with three methods that solve this problem. The ‘forEach’, ‘map’, and ‘reduce’ methods are all higher order functions, or functions that take a separate function as an argument. With these Array methods, the argument is a function referred to as the ‘callback’.
I’ll get around to explaining the details of how to use each of these methods in a minute, but one of the trickiest parts is knowing which one to use in the first place.
To determine which Array iteration method to use, ask yourself: what do I want the immediate result of this operation to be?
forEach
If you ask yourself, “what do I want the immediate result of this operation to be?” and there isn’t an answer that you could store in a single variable, then ‘forEach’ is the method for you. ‘forEach’ doesn’t return a value at all, but it will take each item in the Array in sequence and send it to your callback, along with the index of the item, and a reference to the entire Array. ‘forEach’ is useful when you need to send each item to a separate function, like the console’s ‘log’ function, and we can use it to refactor our first example. We’ll add a slight twist and also log the index of each name in the Array, as well as the length of the Array:
// Use forEach to iterate over each item in the Array,
// calling our callback function with the item's value,
// the item's index, and a reference to the entire Array
// as argumentsvar names = ['Sonny', 'Joel', 'Isabelle', 'Henry'];names.forEach(function(currentValue, index, array) {
console.log(index + ' of ' + array.length + ': ' + currentValue);
});// Logs:
// "0 of 4: Sonny"
// "1 of 4: Joel"
// "2 of 4: Isabelle"
// "3 of 4: Henry"
Pretty cool — no more loop! If the callback function pattern is new to you, don’t worry, you’ll get the hang of it quickly. But keep in mind that the names you assign to your arguments don’t matter, but the order does. So you could rename the variable ‘index’ to ‘i’ and still get the same result:
// Renaming the variable in the callback has no effectvar names = ['Sonny', 'Joel', 'Isabelle', 'Henry'];names.forEach(function(currentValue, i, array) {
console.log(i + ' of ' + array.length + ': ' + currentValue);
});// Still logs:
// "0 of 4: Sonny"
// "1 of 4: Joel"
// "2 of 4: Isabelle"
// "3 of 4: Henry"
However, whatever names you choose for arguments, the first will always be set to the item’s value, the second to the item’s index, and the third to a reference to the entire Array:
// The ORDER of the callback arguments matters, not the namesvar names = ['Sonny', 'Joel', 'Isabelle', 'Henry'];names.forEach(function(index, currentValue, array) {
// Watch out! 'index' now contains the item's value,
// while 'currentValue' now contains the item's index
console.log(index + ' of ' + array.length + ': ' + currentValue);
});// Logs something we didn't want:
// "Sonny of 4: 0"
// "Joel of 4: 1"
// "Isabelle of 4: 2"
// "Henry of 4: 3"
I mentioned that forEach doesn’t return a value, but also note that our callback function doesn’t need to return a value either. If it did return a value ‘forEach’ wouldn’t store it anywhere and we’d have no way to reference it.
But what if we do want our Array operation to return a value? That’s where our next two methods come in.
map
If you ask yourself, “what do I want the immediate result of this operation to be?” and the answer sounds something like, “I want a copy of this Array, but with each value in it modified in some way,” then ‘map’ is the method for you.
‘map’ takes a callback of exactly the same signature as the ‘forEach’ callback, but with ‘map’ our callback needs to return a value. This return value will get stored in the corresponding index of the current item in the iteration, but in a new Array that ‘map’ will eventually return.
Let’s refactor our doubling example using ‘map’:
// Use 'map' to double each value in an Array; produce a
// new Array with the same length as the originalvar numbers = [0, 1, 2, 3];var doubles = numbers.map(function(currentValue, index, array) {
return currentValue * 2;
});// doubles is: [0, 2, 4, 6]
Much cleaner! We’ve fixed the problem of typing the ‘for’ or ‘while’ loop over and over, but what if we wanted to reuse our callback function elsewhere in our application. We’ve been defining our callback as an anonymous function directly in the method invocation, but we can refactor the callback out like so:
// Use 'map' to double each value in an Array, passing
// in a callback by variable referencevar numbers = [0, 1, 2, 3];var double = function(n) {
return n * 2;
};var doubles = numbers.map(double);// doubles is still: [0, 2, 4, 6]
Now we can reuse our callback elsewhere in our application, perhaps as an argument to other higher order functions.
We’ve examined ‘forEach’ and ‘map’; let’s take a look at our last method.
reduce
Once again you’ll start by asking, “what do I want the immediate result of this operation to be?” and if the answer is a single value, we’ll use ‘reduce’. ‘reduce’ takes a callback similar to ‘forEach’ and ‘map’, but with one additional argument. This argument, sometimes called ‘previousValue’ or ‘accumulator’, comes first in the callback’s list of arguments, and it keeps track of the result of whatever we’re doing as we iterate over each item in the array. Our callback needs to return a new value for the accumulator during each iteration. After we’ve iterated over every item in the Array, it’s the value of the accumulator that gets returned by ‘reduce’. Along with the callback, we can pass a second, optional argument to ‘reduce’. This will be the initial value of the accumulator as we enter the callback with the first item in the Array. If we omit this second argument and only pass ‘reduce’ a callback, then the accumulator is set to the value of the first item in the Array, and the iteration starts with the second item in the Array. An obvious use of ‘reduce’ is to find the sum of the numbers in an Array, so let’s take a look at that case first:
// Get the sum of the numbers in an Array using 'reduce'var numbers = [1, 2, 3];var sum = numbers.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 0);// sum is: 6
Here we passed in an initial value of zero for the ‘accumulator’, so our iteration started with the first item in the array. We could’ve left off that second argument to ‘reduce’ and still obtained the same result:
// Get the sum of the numbers in an Array using 'reduce', but
// without passing an initial value for the accumulatorvar numbers = [1, 2, 3];var sum = numbers.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
});// sum is still: 6
Since we didn’t pass an initial value for the accumulator, the accumulator is set to the value of the first item in the array, 1, and ‘reduce’ starts iterating at the second item in the Array.
Up until now, we’ve only used the accumulator and the current item value in our callback. But the callback is invoked with the index of the current item as well as a reference to the entire array, similar to the ‘forEach’ and ‘map’ callbacks. Let’s use the items index to find the sum of the numbers in the Array at even indexes:
// Sum the numbers at even indexesvar numbers = [1, 2, 3];var sum = numbers.reduce(function(accumulator, currentValue, index) {
if (index % 2 === 0) {
return accumulator + currentValue;
} else {
return accumulator;
}
});// sum is: 4
Here 1 and 3 are at even indexes (remember that Array indexes start at 0) and so our sum is 4.
Summing the values in the Array is an obvious use case for ‘reduce’, but remember I said that any time we want a single value as the result of our operation then we need to use ‘reduce’. With that in mind, let’s use it to refactor our code that found the longest word in an Array of Strings. Here it makes sense to think of our accumulator as keeping track of the longest word found so far, so let’s name it ‘longestFound’:
// Use 'reduce' to find the longest word in an Array of Stringsvar words = ['space', 'moon', 'crater'];var longestWord = words.reduce(function(longestFound, currentValue) {
if (currentValue.length > longestFound.length) {
return currentValue;
} else {
return longestFound;
}
});// longestWord is: 'crater'
Our accumulator ‘longestFound’ is automatically set to the value of the first item in the Array, ‘space’. ‘reduce’ starts iterating at the second item, and if that item’s value is longer than ‘longestFound’ then we reset ‘longestFound’ to that item’s value by returning it. We can even refactor out the callback to make our code more modular:
// Return the longest word in an Array of Strings using
// 'reduce' and a reusable callback functionvar words = ['space', 'moon', 'crater'];var longest = function(word1, word2) {
if (word1.length >= word2.length) {
return word1;
} else {
return word2;
}
};var longestWord = words.reduce(longest);// longestWord is still: 'crater'
Here we’ve defined a function ‘longest’ which takes two words and returns the longer of the two (or the first if they have equal lengths). We’re using it as the callback for ‘reduce’, but we can use it again throughout our application.
‘reduce’ has many interesting and perhaps non-obvious use cases. As long as you want your Array operation to result in a single value, you can put ‘reduce’ to work. Suppose we’d like to test our Array to see if a particular value is present. Since the result of that operation is a boolean (‘true’ if the value is present and ‘false’ if it isn’t), and a boolean is a single value, we can use ‘reduce’. Here it’s useful to think of your accumulator as keeping track of whether or not the target value has been found, so we’ll rename it once again:
// Use 'reduce' to test an Array for the presence
// of a specific valuevar words = ['space', 'moon', 'crater'];var alienFound = words.reduce(function(targetFound, currentValue) {
if (currentValue === 'alien') {
return true;
} else {
return targetFound;
}
}, false);var moonFound = words.reduce(function(targetFound, currentValue) {
if (currentValue === 'moon') {
return true;
} else {
return targetFound;
}
}, false);// alienFound is: false
// moonFound is: true
Here our accumulator starts off as ‘false’, but if we iterate over an Array item that matches the word we’re looking for we set it to ‘true’. Otherwise we return the current state of whether or not the target value has been found.
Hopefully by now when you think, “I’d like to get a single value out of this Array operation,” ‘reduce’ comes to mind, but I have a slight twist for you: that single value can also be an Array. If you want the result of the operation to also be an Array, but without a one-to-one relationship between the values in the input Array and the values in the resulting Array, then we can once again use ‘reduce’. Let’s use it to filter an Array of numbers to only the numbers that are evenly divisible by 3. Here our accumulator is the resulting Array, which we’ll call ‘result’ and initially sent to an empty Array literal:
// Use 'reduce' to filter an Array of numbers
// producing an Array of numbers divisible by 3var numbers = [1, 3, 7, 9, 11, 12];var divisibleByThree = numbers.reduce(function(result, currentValue) {
if (currentValue % 3 === 0) {
return result.concat(currentValue);
} else {
return result;
}
}, []);// divisibleByThree is: [3, 9, 12]
As we iterate over the Array, we test the current value to see if it’s evenly divisible by 3. If it is we add it to our ‘result’ Array with ‘concat’ and return the result of that operation as the new accumulator. Otherwise, we leave the accumulator unchanged by simply returning it.
Closing the loop
Using higher order functions to iterate over your Arrays may seem intimidating at first, but with a little practice you’ll find yourself reaching into your virtual toolbox and pulling out ‘forEach’, ‘map’, and ‘reduce’ as easily as you would that ‘for’ loop. In no time you’ll find that you’re producing more modular, reusable, and DRYer code.
If you find a great, unexpected, usual, or surprising use case for ‘forEach’, ‘map’, or ‘reduce’, I’d love to hear about it in the comments!