DEV Community

Samuel Rouse
Samuel Rouse

Posted on

JavaScript Arrays: Filter

Filter - Fewer is Better

When you need to eliminate entries from an array, you are filtering.

Method Returns One-for-one Runs for All
.filter((value, index, array) => Boolean) Array No Yes

The .filter() function takes a predicate – a function that returns true or false. In .map() we get the return value of the mapper function; with .filter() we use the return value of the predicate to determine if the value will appear in the new result array.

Code Comparison

To get a sense for what .filter() does, let's take a look at other ways you might filter an array.

const data = [1, 2, undefined, 3, 'e'];
Enter fullscreen mode Exit fullscreen mode
// Older Imperative way - for loop
function justNumbers(data) {
  const newData = [];

  for (let i = 0; i < data.length; i += 1) {
    if (typeof data[i] === 'number') {
      newData.push(data[i]);
    }
  }

  return newData;
}
Enter fullscreen mode Exit fullscreen mode
// Newer Imperative way - for..of loop
function justNumbers(data) {
  const newData = [];

  for (let value of data) {
    if (typeof value === 'number') {
      newData.push(value);
    }
  }

  return newData;
}
Enter fullscreen mode Exit fullscreen mode
// New Declarative way - .filter()
const justNumbers = (data) => data
  .filter(value => typeof value === 'number');
Enter fullscreen mode Exit fullscreen mode

Use Cases

I strongly recommend pasting some of these examples into a browser console, a code sandbox website, or a playground like RunJS so you can try different changes to see what happens.

Removing Empty/Falsy Entries

If you have an array with undefined or null or other falsy values that you want to remove, you can use the Boolean type function, which will return a Boolean from the input value.

const data = [ { id: 1 }, undefined, { id: 3 }, undefined ];

data.filter(Boolean);
// [ { id: 1 }, { id: 3 } ]
Enter fullscreen mode Exit fullscreen mode

Using .filter(Boolean) may seem odd when most examples use anonymous functions as the predicate, but remember any function can be passed to the array methods.

const makeBoolean = (value) => !!value;

// These two calls to filter work the same
data.filter(makeBoolean);
data.filter((value) => !!value);
Enter fullscreen mode Exit fullscreen mode

If instead you need to remove nullish values but keep falsy values like 0 or '', this simple condition falls down.

const data = [ 0, undefined, true, false ];

data.filter(Boolean);
// [ true ]
Enter fullscreen mode Exit fullscreen mode

We'll need something a bit different for nullish checks

const data = [ 0, undefined, true, false ];

data.filter((value) => value != null);
// [ 0, true, false ]
Enter fullscreen mode Exit fullscreen mode

These two options are very common for cleaning up invalid data.

Removing Duplicates

Sometimes you get more data than you want, even the same data multiple times.

Because the filter predicate receives the value, the index and the original array, we can use these elements to check for other matches outside our current record with different indexes.

const data = [ 1, 2, 4, 2, 5, 3, 1 ];

data.filter((value, index, array) => {
  // We can used indexOf for primitive values
  // True if the first match is also our index
  return array.indexOf(value) === index;
});
// [ 1, 2, 4, 5, 3 ]
Enter fullscreen mode Exit fullscreen mode

For complex objects, we can use other array methods, .find() or .findIndex().

const data = [
  { id: 1, label: 'pear' },
  { id: 2, label: 'banana' },
  { id: 1, label: 'pear' },
  { id: 3, label: 'apple' },
];

// Similar index check as with the primitive value filter
data.filter((value, index, array) => {
  // findIndex uses a predicate similar to filter
  return array.findIndex((entry) => entry.id === value.id) === index;
});

// Using .find() works...most of the time
data.filter((value, index, array) => {
  // This time we see if the found object is the same object
  return array.find((entry) => entry.id === value.id) === value;
});
Enter fullscreen mode Exit fullscreen mode

I generally recommend against the .find() version. It can be a little harder to follow because we're matching objects rather than indices, so when looking at the data it can be unclear if two elements are the same object or just contain the same data. This uncertainty can create unexpected risk: if one object appears multiple times, it may not filter correctly. Try the two filter actions above with the data below to see the difference.

const pear = { id: 1, label: 'pear' };
const banana = { id: 2, label: 'banana' };
const apple = { id: 3, label: 'apple' };

// The same object is used twice
const data = [ pear, banana, pear, apple ];
Enter fullscreen mode Exit fullscreen mode

Reject

Now that we have some examples of matching predicates, sometimes we want the exact opposite; we want to reject the matches.

Sure we could write the opposite predicate filter function, but if we're inside a library or utility that accepts a filter function rather than writing it inline, being able to "do the opposite" can be convenient.

Underscore or Lo-dash provide this to simplify your life, but implementing this is fairly straightforward.

Let's take the example from Removing Duplicates, and see what we need to do to invert the result. First, let's isolate the predicate.

const data = [ 1, 2, 4, 2, 5, 3, 1 ];

// We can used indexOf for primitive values
// True if the first match is also our index
const getFirstInstance = (value, index, array) => {
  return array.indexOf(value) === index;
};

// Now, use the defined function as a regular filter
data.filter(getFirstInstance);
// [ 1, 2, 4, 5, 3 ]
Enter fullscreen mode Exit fullscreen mode

Now that we have the predicate, let's see how we can invert the response.

const data = [ 1, 2, 4, 2, 5, 3, 1 ];

const getFirstInstance = (value, index, array) => {
  return array.indexOf(value) === index;
};

// An explicit wrapper to invert the predicate result
data.filter((value, index, array) => !getFirstInstance(value, index, array));
// [ 2, 1 ]
Enter fullscreen mode Exit fullscreen mode

We can use a little functional programming style to simplify this into a small higher order function called: not.

const not = (predicate) => (...args) => !predicate(...args);
Enter fullscreen mode Exit fullscreen mode

Let's put it together and see "reject"

const data = [ 1, 2, 4, 2, 5, 3, 1 ];

const not = (predicate) => (...args) => !predicate(...args);
const getFirstInstance = (value, index, array) => {
  return array.indexOf(value) === index;
};

// Use an HOF to invert the predicate
data.filter(not(getFirstInstance));
// [ 2, 1 ]
Enter fullscreen mode Exit fullscreen mode

Duplicate Performance

These examples are not very efficient in how they iterate over the array. As the size of your data increases, you may experience performance concerns if you are looping over the array once for each value. I don't recommend worrying about it right away, but it is something to keep in mind. There are utilities that substantially improve on this simple filter concept, should you need them.

Summary

Filter allows you to extract or eliminate specific entries from your array of data. Whether your are filtering for undefined or specific primitive values or reaching into nested objects to check values or even run calculations, the predicate passed to .filter() can be a very powerful tool.

Top comments (0)