Using the filter() Method in JavaScript

Abstract image used to represent Using the filter() Method in JavaScript
Image by Simone Hutsch.

It's funny; after decades of working in web development, one of the things that I still always have to do a quick Google to check on is how you filter an array of objects based on a property of that object. And it's something that crops up very regularly in frontend development...

So today, I intend to write about JavaScript's filter() in the hopes that this time around, its syntax will sink in, and if not, at least I'll know where to look for a quick sanity check next time!


Understanding filter()

JavaScript's filter() method is what we need to filter an array of objects based on an object's property. It allows us to create a new array filled with the elements from the first array that pass a specific test. Essentially, generate a subset of the first array for objects that return true for a given condition, which is great for filtering data (the clue was in the name really wasn't it?).

Fundamentals

filter() belongs to Array.prototype, giving it the full name Array.prototype.filter(). It calls the callback for each existing array position in order, skipping empty slots in a sparse array.

The callback's return value is tested for truthiness:

  • A truthy value, including true, keeps the element.
  • A falsy value, including false, 0 or undefined, leaves it out.

filter() creates a new shallow array and does not itself rewrite the input. That does not make every call pure: a callback can have side effects or mutate an object. Included objects are still the same references held in the original array, so changes through either array can affect them.

Syntax

The syntax for filter() looks like this:

const array = [-2, 0, 3];
const thisValue = { minimum: 0 };

const newArray = array.filter(function (element, index, arr) {
  return element > this.minimum;
}, thisValue);

console.log(newArray);  // [3]

This can be broken down like this:

  • element: The current element being processed in the array;
  • index (optional): The index of the current element being processed;
  • arr (optional): The array filter was called upon;
  • thisValue (optional): A value to use as this when executing the callback.

A Simple Example

As a simple example, and one of the use cases I came across on a previous project, say we have a dataset that contains car makes and models. We could use filter() to extract a subset of this data to find only models built by a specific manufacturer:

const cars = [
  { brand: 'Audi', model: 'A3' },
  { brand: 'Volvo', model: 'XC40' },
  { brand: 'Audi', model: 'Q2' },
  { brand: 'Volvo', model: 'C30' },
  { brand: 'Volkswagen', model: 'Polo' },
  { brand: 'Audi', model: 'RS5' },
  { brand: 'Volvo', model: 'S60' },
  { brand: 'Volkswagen', model: 'Tiguan' },
  { brand: 'Volkswagen', model: 'Golf' },
];

const volvoCars = cars.filter(({ brand }) => brand === 'Volvo');

console.log(volvoCars);
/*
  Output:
  [
    { brand: 'Volvo', model: 'XC40' },
    { brand: 'Volvo', model: 'C30' },
    { brand: 'Volvo', model: 'S60' }
  ]
*/

Here, we iterate over the cars array and include only those cars in volvoCars where the brand model is 'Volvo'. What comes out the other side is an array of three cars made by Volvo.

I should mention here that I've also opted to use destructuring in the example above where we reference brand directly. This could just as easily be written like this:

const volvoCars = cars.filter((car) => car.brand === 'Volvo');

A More Complicated Example

For the sake of full disclosure, this is actually a task I have often set for candidates during interviews.

The Challenge

We have an array of books; these should be filtered to generate an array of books from this dataset, which are either in the 'Science Fiction' or 'Disaster' genre and should be published after 1900.

The Dataset

const books = [
  {
    title: 'The War of the Worlds',
    author: 'H.G. Wells',
    genres: ['Science Fiction', 'Disaster'],
    year: 1898,
  },
  {
    title: 'The Martian',
    author: 'Andy Weir',
    genres: ['Science Fiction'],
    year: 2011,
  },
  {
    title: 'Dune',
    author: 'Frank Herbert',
    genres: ['Science Fiction'],
    year: 1965,
  },
  {
    title: 'The Road',
    author: 'Cormac McCarthy',
    genres: ['Fiction', 'Dystopian'],
    year: 2006,
  },
  {
    title: 'On the Beach',
    author: 'Nevil Shute',
    genres: ['Science Fiction', 'Disaster'],
    year: 1957,
  },
  {
    title: 'Pride and Prejudice',
    author: 'Jane Austen',
    genres: ['Fiction', 'Romance'],
    year: 1813,
  },
  {
    title: 'The Hobbit',
    author: 'J.R.R. Tolkien',
    genres: ['Fantasy'],
    year: 1937,
  },
  {
    title: 'Seveneves',
    author: 'Neal Stephenson',
    genres: ['Science Fiction'],
    year: 2015,
  },
];

For the sake of simplicity, we assume this data is typed, so there's no attempt to trip up the candidate with numbersasstrings or optional data:

title: string;
author: string;
genres: ('Science Fiction' | 'Fantasy' | 'Fiction' | 'Romance' | 'Disaster' | 'Dystopian')[];
year: number;

The Solution

This is where an understanding of filter() and how to structure conditions becomes important.

const filteredBooks = books.filter(
  (book) =>
    (book.genres.includes('Science Fiction') ||
      book.genres.includes('Disaster')) &&
    book.year > 1900
);

As I mentioned above, you could also use destructuring here:

const filteredBooks = books.filter(
  ({ genres, year }) =>
    (genres.includes('Science Fiction') || genres.includes('Disaster')) &&
    year > 1900
);

Explanation

Regardless of whether you choose to destructure or not, what we're doing in both cases is using a callback function for the filter() which checks for:

  • Genre:

    true if the genre field contains either 'Science Fiction' or 'Disaster';
  • Year:

    true if the year is larger than 1900.

If both checks are true, then the book meets our requirements and is included in the filteredBooks array. Notably, the result won't include:

  • The Hobbit

    : it's the wrong genre;
  • Pride and Prejudice

    : also the wrong genres;
  • The War of the Worlds

    : it was published before 1900.

If you're faced with this at interview, then you'll get bonus points if you can tell me whether you prefer The Martian or Seveneves. There's no right answer, although I am sore that Seveneves hasn't yet been picked up for the Blockbuster treatment...


Tips for Using filter()

Chaining Methods

filter() can be chained with other array methods like map() and reduce() for more complex operations.

Performance

filter() visits the existing array positions and creates a result array. Chaining several filters can be readable, but it also adds traversals and intermediate arrays. If performance matters, measure the real predicates and data; cheap checks that reject many items can often shortcircuit more expensive checks within one callback.

Complex Conditions

A callback can combine several conditions or inspect nested properties. Returning a boolean usually makes that intent clearest, even though filter() accepts any truthy or falsy result. Keep side effects out of the predicate where you can.


Wrapping Up

Hopefully, having written this all out, I won't need to go check Google next time I need to filter a dataset... Hopefully, you won't either!


Need a senior engineer involved?

I can work directly in the codebase, review the architecture, or support the team through delivery when the work needs more than extra hands.