DEV Community

Vince Campanale
Vince Campanale

Posted on

Harnessing the power of Javascript's .map and .filter

Functional programming has been a hot topic as of late. JavaScript is a multi-paradigm language and one of the paradigms available to us lucky JS devs is the functional paradigm. If you think this stuff is cool, go google Elm when you're done with this article ;)

Disclaimer: I'm still learning myself, so some of the terms I use may not be precise or match the academic / proper definition. If you spot an error, please throw an exception in the comments. That being said, I'm confident about the big idea of this post so read on!

Map & filter are two crucial methods in functional programming. You don't have to know anything about functional programming to know how to use them and I guarantee you that these two little functions will improve your code.

Let's learn by example. This exercise is taken straight from a tutorial I'm currently working through called "Functional Programming in Javascript," which is intended to serve as an introduction to RxJs (Reactive Extensions). Check it out when you're done reading this article. I'm going to solve the same problem in a couple different ways to show the true beauty of .map() and .filter().

Here we have some data containing a list of newly released movies in JSON format:

//Excellent test data from http://reactivex.io/learnrx/
 var newReleases = [
     {
       "id": 70111470,
       "title": "Die Hard",
       "boxart": "http://cdn-0.nflximg.com/images/2891/DieHard.jpg",
       "uri": "http://api.netflix.com/catalog/titles/movies/70111470",
       "rating": 4.0,
       "bookmark": []
     },
     {
       "id": 654356453,
       "title": "Bad Boys",
       "boxart": "http://cdn-0.nflximg.com/images/2891/BadBoys.jpg",
       "uri": "http://api.netflix.com/catalog/titles/movies/70111470",
       "rating": 5.0,
       "bookmark": [{ id: 432534, time: 65876586 }]
     },
     {
       "id": 65432445,
       "title": "The Chamber",
       "boxart": "http://cdn-0.nflximg.com/images/2891/TheChamber.jpg",
       "uri": "http://api.netflix.com/catalog/titles/movies/70111470",
       "rating": 4.0,
       "bookmark": []
     },
     {
       "id": 675465,
       "title": "Fracture",
       "boxart": "http://cdn-0.nflximg.com/images/2891/Fracture.jpg",
       "uri": "http://api.netflix.com/catalog/titles/movies/70111470",
       "rating": 5.0,
       "bookmark": [{ id: 432534, time: 65876586 }]
     }
   ];
Enter fullscreen mode Exit fullscreen mode

Each movie has several properties: id, title, boxart, uri, rating, and bookmark (an array of JSON objects).

In this tutorial, I'm going to solve a simple problem: Collect the IDs of movies with 5.0 ratings.

💓 For the Love of Loops 💓

The first way I will solve this problem makes use of our oldest friend, the humble for loop.

var counter,
  otherCounter,
  favorites = [],
  favoriteIds = [];

for ( counter = 0; counter < newReleases.length; counter++ ) {
  if ( newReleases[counter].rating === 5.0 ) {
    favorites.push(newReleases[counter]);
  }
}

for ( otherCounter = 0; otherCounter < favorites.length; otherCounter++ ) {
  favoriteIds.push(favorites[otherCounter].id);
}
Enter fullscreen mode Exit fullscreen mode

Lovely. This gets the job done, but I have three problems with this code:

1 There's a lot of code here to do a simple task.

2 We're making a lot of variables to track our values, which is pretty wasteful memory-wise.

3 Does it really matter that we traverse the movie list from the beginning to end? Couldn't we do it in any order? Do we really need to explicitly spell that out in our code?

Ask yourself number three and really sit and contemplate that for a moment. When we use the for loop to tell an iterator to traverse an array, we have to spell out in code the order in which the array is traversed. This is useful sometimes, but most of the time we're just going from beginning to end -- smells like an opportunity for abstraction to me.

For Each or Not For Each 📖

.forEach() abstracts the explicit logic of the for loop away. We call .forEach() on our newReleases array and trust that the computer will traverse the array. The computer can traverse the array beginning to end, end to beginning, middle out, upside-down, it really doesn't matter. The point is: we don't have to tell the computer about how the array is traversed -- we just know we're going to do something on each element of the array. That's where the iterator function comes in. The iterator function is our instruction to the computer about what should happen when the iterating mechanism (the hidden / implied for loop) encounters each element in the array. For example, let's say we want to check if a movie has a rating of 5 stars and push it to a new array called favorites if it does. Our function would look like this:

function (movie) {
  if ( movie.rating === 5.0) {
    favorites.push(movie);
  }
}
Enter fullscreen mode Exit fullscreen mode

By passing this function as an iterator to .forEach(), we run it on every element in the array.

var favorites = [],
    favoriteIds = [];

newReleases.forEach(function(movie) {
  if ( movie.rating === 5.0 ) {
    favorites.push(movie);
  }
});

favorites.forEach(function(movie) {
  favoriteIds.push(movie.id);
});
Enter fullscreen mode Exit fullscreen mode

Unfortunately, the problems I had with the for loop solution remain with the .forEach() solution.

1) Still a lot of code for such a simple task.

2) Still using variables to hold values as we go along.

3) We may have gotten rid of the explicit `for` loops, but I still see the word "for" in there. The extra code defining the order of traversal is gone, but we're still saying "for each element in this array, do something." I think the fact that we want to apply this function to each element should be *implied*.  
Enter fullscreen mode Exit fullscreen mode

Introducing the 🌟Stars🌟 of the Show

Time to use .map() and .filter() to get the job done. Now that we understand exactly what needs to be done to solve this problem, it should be easy to reverse understand what .map() and .filter() do for us.

.map() and .filter() are just unique variations on the classic .forEach(). The nice thing about them is that they handle a specific case for us so we don't have to bother telling the computer "for this element, do this". It just goes without saying that we want each element of the collection to be processed by the reducer function (the same thing as the iterator function in .forEach()).

.filter() is used when we want to *ahem* filter each element in the collection based on a certain condition.

.map() is used when we want to change each element in the array in some way. We're "mapping" each element from one value to another.

The moment we've all been waiting for:

var favorites = newReleases.filter(function(movie) {
  return movie.rating === 5.0;
});

var favoriteIds = favorites.map(function(movie) {
  return movie.id;
});
Enter fullscreen mode Exit fullscreen mode

Hmmm... better...

Let's look at our original pain points and compare:

1) I still think we could do this with less code.

2) Still skeptical about the need for two variables to compute one value...

3) ✔️ No more "for"! I'd say this problem is solved.  
Enter fullscreen mode Exit fullscreen mode

Whew, abstracting away that for loop took some effort, but it's officially taken care of now. We're almost done, I promise.

FILTER🔗MAP

Method chaining is a wonderful thing.

var favoriteIds = newReleases
  .filter(function(movie) {
    return movie.rating === 5.0;
  })
  .map(function(movie) {
    return movie.id;
  });
Enter fullscreen mode Exit fullscreen mode

That takes care of number 2.

1) Still a bit verbose. I think we could sweeten this up with some syntactic sugar.*

2) ✔️ One value. One variable.

3) ✔️ No more "for"!
Enter fullscreen mode Exit fullscreen mode

*Note: Despite what some may believe, arrow functions in Javascript are much more than mere syntactic sugar. Just wanted to use the 'sweeten' joke.

ARROWS ↗️ ⬅️ ⬆️ ➡️ ↘️

Let's shorten this with some ES6 arrows.

var favoriteIds = newReleases.
  filter( movie => { return movie.rating === 5.0 }).
  map( movie => { return movie.id });
Enter fullscreen mode Exit fullscreen mode

Abbreviated Variables, const, & Implicit Return

Proceed with caution. Someone call the fire department. 🚒

const favIds = newReleases.filter( m => m.rating === 5.0 ).map( m => m.id );
Enter fullscreen mode Exit fullscreen mode
1) ✔️ Short & sweet.

2) ✔️ One value. One variable.

3) ✔️ No more "for"!
Enter fullscreen mode Exit fullscreen mode

Aren't .map() & .filter() the best?

To Learn More:

     Here's the link to the tutorial I got this problem from: http://reactivex.io/learnrx/.

Top comments (6)

Collapse
 
valtism profile image
Dan Wood

Thanks for the article, very well presented. Can you tell me some more about why you would use const instead of a variable?

Collapse
 
vincecampanale profile image
Vince Campanale • Edited

Thanks! I'm glad you liked it.

I'm not sure I grasp your question fully, but const is a way of declaring a variable. The reason I prefer it to var is because it offers a more reliable mechanism for instantiating variables. When a variable does not need to be reassigned within the scope it is defined in, a const variable is always preferred. Using const, you avoid common trap doors like accidental global variables, naming conflicts, and the like. Does this answer your question?

Collapse
 
valtism profile image
Dan Wood

Ah, so it is more for explicitly defining what it does than it is for memory management.

Collapse
 
chrisvasqm profile image
Christian Vasquez

This is, by far, the best explanation of how and why .filter() and .map() are useful 👏. I finally understand it.

Thanks a lot for making it, Vince!

Collapse
 
vincecampanale profile image
Vince Campanale

Glad I could help man!

Collapse
 
janguianof profile image
Jaime Anguiano

niiice!!