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 thefilter
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 JavaScriptfor
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 thefilter
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 ofconst
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 usingmap
, 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 thefilter
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.
Frequently Asked Questions (FAQs) about Filtering and Chaining in Functional JavaScript
What is the concept of filtering in JavaScript?
Filtering in JavaScript is a powerful feature that allows you to manipulate arrays. It is a built-in method of the array object. The filter() method creates a new array with all elements that pass a test implemented by the provided function. It does not mutate the original array. This method is commonly used when you want to create a new array from a source array, selecting only those elements that meet certain criteria.
How does chaining work in JavaScript?
Chaining in JavaScript is a technique where you link several functions, calling them one after another. The output of one function becomes the input of the next function. This is possible because many JavaScript methods return an object, allowing the calls to be chained together. Chaining can make your code more readable and concise.
Can you provide an example of filtering in JavaScript?
Sure, let’s consider an array of numbers and we want to filter out the numbers less than 10. Here’s how you can do it:let numbers = [1, 5, 10, 15, 20];
let filteredNumbers = numbers.filter(number => number < 10);
console.log(filteredNumbers); // Output: [1, 5]
Can you provide an example of chaining in JavaScript?
Absolutely, let’s consider an array of numbers and we want to filter out the numbers less than 10 and then multiply them by 2. Here’s how you can do it:let numbers = [1, 5, 10, 15, 20];
let result = numbers.filter(number => number < 10).map(number => number * 2);
console.log(result); // Output: [2, 10]
What are the advantages of using filter and chaining in JavaScript?
The filter and chaining methods in JavaScript provide a more declarative and readable way to handle arrays. They allow you to write cleaner and more concise code, improving maintainability. They also do not mutate the original array, preventing side effects.
Are there any limitations or drawbacks to using filter and chaining in JavaScript?
While filter and chaining are powerful features, they can have performance implications if used improperly. For large arrays, the filter method can be slow because it has to iterate over each element in the array. Similarly, chaining multiple methods can also be slow because each chained method results in a new array being created.
Can I use filter and chaining with other data types in JavaScript?
The filter and chaining methods are specifically designed for arrays. However, with some additional steps, you can use similar concepts with other data types. For example, you can convert an object to an array, perform the filtering and chaining operations, and then convert it back to an object.
How does the filter method determine what to include in the new array?
The filter method uses a callback function that you provide. This function should return either true or false for each element in the array. If the function returns true, the element is included in the new array. If it returns false, the element is not included.
Can I chain multiple filter methods together?
Yes, you can chain multiple filter methods together. Each filter method will create a new array that can be the input for the next filter method. This can be useful if you have complex filtering criteria that are easier to express as multiple separate conditions.
Can I use filter and chaining with arrow functions in JavaScript?
Yes, you can use arrow functions with the filter and chaining methods. In fact, arrow functions can make your code more concise and easier to read when using these methods. The arrow function syntax is particularly useful for simple callback functions that just return the result of a single expression.
I've worked as a Web Engineer, Writer, Communications Manager, and Marketing Director at companies such as Apple, Salon.com, StumbleUpon, and Moovweb. My research into the Social Science of Telecommunications at UC Berkeley, and while earning MBA in Organizational Behavior, showed me that the human instinct to network is vital enough to thrive in any medium that allows one person to connect to another.