Filtering and Chaining in Functional JavaScript
This article was peer reviewed by Dan Prince, Vildan Softic and Joan Yinn. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
One of the things I appreciate about JavaScript is its versatility. JavaScript gives you the opportunity to use object oriented programming, imperative programming, and even functional programming. And you can go back and forth among them depending on your current needs and the preferences and expectations of your team.
Although JavaScript supports functional techniques, it’s not optimized for pure functional programming the way a language such as Haskell or Scala is. While I don’t usually structure my JavaScript programs to be 100 percent functional, I enjoy using functional programming concepts to help me keep my code clean and focus on designing code that can be reused easily and tested cleanly.
Filtering to Limit a Data Set
With the advent of ES5, JavaScript Arrays inherited a few methods that make functional programming even more convenient. JavaScript Arrays can now map, reduce, and filter natively. Each of these methods goes through every one of the items in an array, and without the need for a loop or local state changes, performs an analysis that can return a result that’s ready to use immediately or pass-through to be operated on further.
In this article I want to introduce you to filtering. Filtering allows you to evaluate every item of an array, and based on a test condition you pass in, determine whether to return a new array that contains that element. When you use the filter
method of Array, what you get back as another array that is either the same length as the original array or smaller, containing a subset of the items in the original that match the condition you set.
Using a Loop to Demonstrate Filtering
A simple example of the sort of problem that might benefit from filtering is limiting an array of strings to only the strings that have three characters. That’s not a complicated problem to solve, and we can do it pretty handily using vanilla JavaScript for
loops without the filter
method. It might look something like this:
var animals = ["cat","dog","fish"];
var threeLetterAnimals = [];
for (let count = 0; count < animals.length; count++){
if (animals[count].length === 3) {
threeLetterAnimals.push(animals[count]);
}
}
console.log(threeLetterAnimals); // ["cat", "dog"]
What we’re doing here is defining an array containing three strings, and creating an empty array where we can store just the strings that only have three characters. We’re defining a count variable to use in the for
loop as we iterate through the array. Every time that we come across a string that has exactly three characters, we push it into our new empty array. And once were done, we just log the result.
There’s nothing stopping us from modifying the original array in our loop, but by doing that we would permanently lose the original values. It’s much cleaner to create a new array and leave the original untouched.
Using the Filter Method
There’s nothing technically wrong with the way that we did that, but the availability of the filter
method on Array allows us to make our code much cleaner and straightforward. Here’s an example of how we might’ve done the exact same thing using the filter
method:
var animals = ["cat","dog","fish"];
var threeLetterAnimals = animals.filter(function(animal) {
return animal.length === 3;
});
console.log(threeLetterAnimals); // ["cat", "dog"]
As before, we started with a variable that contains our original array, and we defined a new variable for the array that’s going to contain just the strings that have three characters. But in this case, when we defined our second array, we assigned it directly to the result of applying the filter
method to the original animals array. We passed filter
an anonymous in-line function that only returned true
if the value it was operating on had a length of three.
The way the filter
method works, it goes through every element in the array and applies the test function to that element. If the test function returns true
for that element, the array returned by the filter
method will include that element. Other elements will be skipped.
You can see how much cleaner the code looks. Without even understanding ahead of time what filter
does, you could probably look at this code and figure out the intention.
One of the happy by-products of functional programming is the cleanliness that results from reducing the amount of local state being stored, and limiting modification of external variables from within functions. In this case, the count
variable and the various states that our threeLetterAnimals
array was taking while we looped through the original array were simply more state to keep track of. Using filter
, we’ve managed to eliminate the for
loop as well as the count
variable. And we’re not altering the value of our new array multiple times the way we were doing before. We’re defining it once, and assigning it the value that comes from applying our filter
condition to the original array.
Other Ways to Format a Filter
Our code can be even more concise if we take advantage of const
declarations and anonymous inline arrow functions. These are EcmaScript 6 (ES6) features that are supported now in most browsers and JavaScript engines natively.
const animals = ["cat","dog","fish"];
const threeLetterAnimals = animals.filter(item => item.length === 3);
console.log(threeLetterAnimals); // ["cat", "dog"]
While it’s probably a good idea to move beyond the older syntax in most cases, unless you need to make your code match an existing codebase, it’s important to be selective about it. As we get more concise, each line of our code gets more complex.
Part of what makes JavaScript so much fun is how you can play with so many ways to design the same code to optimize for size, efficiency, clarity, or maintainability to suit your team’s preferences. But that also puts a greater burden on teams to create shared style guides and discuss the pros and cons of each choice.
In this case, to make our code more readable and more versatile, we might want to take that anonymous in-line arrow function and turn it into a traditional named function, passing that named function right into the filter
method. The code might look like this:
const animals = ["cat","dog","fish"];
function exactlyThree(word) {
return word.length === 3;
}
const threeLetterAnimals = animals.filter(exactlyThree);
console.log(threeLetterAnimals); // ["cat", "dog"]
All we’ve done here is extract the anonymous in-line arrow function we defined above and turn it into a separate named function. As we can see, we have defined a pure function that takes the appropriate value type for the elements of the array, and returns the same type. We can just pass the name of that function directly to the filter
method as a condition.
Quick Review of Map and Reduce
Filtering works hand-in-hand with two other functional Array methods from ES5, map
and reduce
. And thanks to the ability to chain methods in JavaScript, you can use this combination to craft very clean code that performs some pretty complex functions.
As a quick reminder, the map
method goes through every element in an array and modifies it according to a function, returning a new array of the same length with modified values.
const animals = ["cat","dog","fish"];
const lengths = animals.map(getLength);
function getLength(word) {
return word.length;
}
console.log(lengths); //[3, 3, 4]
The reduce
method goes through an array and performs a series of operations, carrying the running result of those operations forward in an accumulator. When it’s done, it returns a final result. In this case we’re using the second argument to set the initial accumulator to 0.
const animals = ["cat","dog","fish"];
const total = animals.reduce(addLength, 0);
function addLength(sum, word) {
return sum + word.length;
}
console.log(total); //10
All three of these methods leave the original array untouched, as they should for proper functional programming practice. If you want a reminder about how map
and reduce
work, you can check out my earlier article on using map and reduce in functional JavaScript.
Chaining Map, Reduce, and Filter
As a very simple example of what’s possible, let’s imagine that you wanted to take an array of strings, and return a single string containing only the three letter strings from the original, but you wanted to format the resulting string in StudlyCaps. Without using map
, reduce
, and filter
, you might try do it something like this:
const animals = ["cat","dog","fish"];
let threeLetterAnimalsArray = [];
let threeLetterAnimals;
let item;
for (let count = 0; count < animals.length; count++){
item = animals[count];
if (item.length === 3) {
item = item.charAt(0).toUpperCase() + item.slice(1);
threeLetterAnimalsArray.push(item);
}
}
threeLetterAnimals = threeLetterAnimalsArray.join("");
console.log(threeLetterAnimals); // "CatDog"
Of course this works, but as you can see were creating a bunch of extra variables that we don’t need, and maintaining the state of an array that’s being changed as we go through our different loops. We can do better.
And in case you’re wondering about the logic behind the variable declarations, I prefer to use let
to declare an empty target array, although technically it could be declared as a const
. Using let
reminds me that the content of the array is going to be altered. Some teams may prefer to use const
in cases like these, and it’s a good discussion to have.
Let’s create some pure functions that take strings and return strings. Then we can use those in a chain of map
, reduce
, and filter
methods, passing the result from one onto the next this way:
const animals = ["cat","dog","fish"];
function studlyCaps(words, word) {
return words + word;
}
function exactlyThree(word) {
return (word.length === 3);
}
function capitalize(word) {
return word.charAt(0).toUpperCase() + word.slice(1);
}
const threeLetterAnimals = animals
.filter(exactlyThree)
.map(capitalize)
.reduce(studlyCaps);
console.log(threeLetterAnimals); // "CatDog"
In this case we define three pure functions, studlyCaps
, exactlyThree
, and capitalize
. We can pass these functions directly to map
, reduce
, and filter
in a single unbroken chain. First we filter our original array with exactlyThree
, then we map the result to capitalize
, and finally we reduce the result of that with studlyCaps
. And we’re assigning the final result of that chain of operations directly to our new threeLetterAnimals
variable with no loops and no intermediate state and leaving our original array untouched.
The resulting code is very clean and easy to test, and provides us with pure functions that we could easily use in other contexts or modify as requirements change.
Filtering and Performance
It’s good to be aware that the filter
method is likely to perform just a tiny bit slower than using a for
loop until browsers and JavaScript engines optimize for the new Array methods (jsPerf).
As I’ve argued before, I recommend using these functional Array methods anyway, rather than using loops, even though they currently tend to be a little bit slower in performance. I favor them because they produce cleaner code. I always recommend writing code in the way that’s the cleanest and most maintainable, and then optimizing only when real-world situations prove that you need better performance. For most use cases I can foresee, I wouldn’t expect filter performance to be a significant bottleneck in a typical web application, but the only way you can be sure is to try it and find out.
The fact that filtering can be slightly slower than using a for
loop is very unlikely to cause a noticeable performance issue in the real world. But if it does, and if your users are negatively impacted, you’ll know exactly where and how to optimize. And the performance will only get better as the JavaScript engines optimize for these new methods.
Don’t be afraid to start filtering today. The functionality is native in ES5, which is almost universally supported. The code you produce will be cleaner and easier to maintain. Using the filter
method you can be confident that you won’t alter the state of the array that you’re evaluating. You will be returning a new array each time, and your original array will remain unchanged.
Agree? Disagree? Comments are welcome below.