Understanding Array.Reduce by Filtering an Array with an Array of Filters in JavaScript

Ambrose Little
Software Developer
Published in
6 min readMay 6, 2017

So you have an array in JavaScript that you want to filter, so of course you can use the filer method on the array. But what if you have more than one filter you want to apply. Again, of course you can just chain it, like myArray.filter(...).filter(...). But what if you have an unknown set of filters you want to apply? Then you want to use an array of filters to filter an array. That’s a mouthful.

Naturally, you could write some looping code, but that’s not very functional, and functional is what all the cool kids are doing these days. It may not be immediately obvious, if you’re not already zenly one with FP (like me!), but you can use Array.reduce to solve this dilemma.

You can skip to the solution in this fiddle (explanation below):

Most of it should be relatively straightforward. First, just setup the test data and search values.

let 
data = [
{ name: 'myFile.txt', category: 'Files'}
, { name: 'Backup', category: 'Folders'}
, { name: 'Main Hard Drive', category: 'Disks'}
, { name: 'Backup Hard Drive', category: 'Disks'}
, { name: 'Thumb Drive', category: 'Disks'}
]
, filteredData = []
, filters = []
, nameFilterValue = 'Hard'.toLowerCase()
, categoryFilterValue = 'Disks'.toLowerCase()
, findInString =
(search, forVal) => search.toLowerCase().indexOf(forVal) >= 0
;

The only non-setuppy type thing in here is the findInString function, which is used later. Again, this is used in order to be a bit more functional, but mostly to keep the code DRY in terms of how we’re doing string matching, since both of the filters in the example use that. (Note: If some of the syntax, like let and the => are unfamiliar, those are in ECMAScript 6/2015. Be sure to read up on those to fully understand the code here.)

if (nameFilterValue) {
filters.push(
item => findInString(item.name, nameFilterValue)
);
}
if (categoryFilterValue) {
filters.push(
item => findInString(item.category, categoryFilterValue)
);
}

This also is boilerplate and in a real app should probably be refactored a bit. (Actually, this whole thing could be refactored into a more plug-n-play module, but let’s not get too fancy here.) So what this does is just check to see if a given filter value has been supplied. This is kind of the whole point of this approach — you could add an arbitrary number of distinct filters with this pattern. If there is a value to filter, add the filter function to the filters array.

filteredData = (!filters.length) ? data // shortcut if no filters
: filters.reduce(
(results, filter) => results.filter(filter)
, data
);

This is where the “magic” happens. First, we do a simple check to see if there are any filters to apply. If not, we just return the whole data set. Otherwise, we filter using Array.reduce.

For the uninitiate, reduce can be a bit misleading. Most of the examples you find deal with “reducing an array to a single value” as they say, so they often are using some kind of sum to show it off. Technically here we are also reducing to a single value, so “reduce” is kind of an apt term, but we are not reducing the set of filters but rather the data set that we are passing in as the initialValue. Let’s go through it step-by-step.

As the docs say, the reduce function just takes two parameters:

arr.reduce(callback, [initialValue])

In this example, the arguments passed to those parameters are:

filters.reduce(
(results, filter) => results.filter(filter)
, data
)

The second argument (the initial value) is simply the full data set — that’s what we want start filtering with.

The first argument passed is the callback for reduce to use. The callback signature looks like this in the docs:

callbackFunc(accumulator, currentValue, currentIndex, array)

This callback is called for each element in the array that you call reduce on, which in this example is our array of filters. So for each filter, call this function:

(results, filter) => results.filter(filter)

Our example doesn’t care about the currentIndex or the array, which is probably true for most uses of reduce — the important things are the accumulator (the filtered data set in our case) and currentValue (a filter function in our case).

A lot of folks (judging by articles posted on it) struggle with the reduce flow/concept, especially when the 50-cent word accumulator is used. This term can also obscure the meaning because it implies you are always accumulating something. In our case here, we are filtering something. IMO, returnedValueFromLastCallbackCall might be a better parameter name. As you can see in the following diagram, that’s what’s going on with reduce.

Array.Reduce Accumulator Flow

All reduce does is call the callback you give it for each element, giving your callback the returned value from the last call to the callback, along with the other, more self-explanatory parameters (that is, the current array value, index, and the array being iterated over). Your job in the callback is to return a new value that you want passed in with the next call or, if there are no more elements, returned to the caller of reduce.

Your job in the callback is to return a new value that you want passed in with the next call or, if there are no more elements, returned to the caller of reduce.

Also not pictured above is that for the first element in the array, whatever you give for initialValue to Array.reduce is passed in as the accumulator. This is a fuller picture:

Array.Reduce Flow

Another way to think of the accumulator is this is the value you ultimately want to return when all is said and done. As you can see, whatever you return from the last call to your callback is what will be returned to the caller. (In fact, you could return anything — including something totally unrelated and unaccumulated; it totally depends on the logic you put in your callback. You can see that accumulator is really not a great name.)

So back to this example:

(results, filter) => results.filter(filter)

As noted already, for each element in the filters array, that is for each filter, our callback function will call Array.filter on the accumulator (named results in our case). So the first time in, results will be the full data set. We call filter on the full data set using the first filter in the filters array — remember it could be any filter in our arbitrary list of filters. That call to Array.filter is then returned from our callback and then passed into the next call (as shown in the flow diagram above). So if both our potential filters (category and name) are in play, the second call to our callback would receive the data set that was filtered from the previous call, and then it could further filter that set, and so on, for as many filters we have, ultimately returning a data set that has passed through all of our filters.

Any time you want to iterate over an array and produce some single result leveraging the information in your array, reduce is your go to function.

And that’s it. The rest of the code in the example is just to show the results in HTML in the Fiddle. You can change the filter values (or set to null/empty string to not use a particular filter) and rerun the Fiddle to see the different results. Now you can hopefully see how Array.reduce can be used in more creative ways, not just for accumulating totals. Any time you want to iterate over an array and produce some single result leveraging the information in your array, reduce is your go to function.

--

--

Ambrose Little
Software Developer

Experienced software and UX guy. Staff Software Engineer at Built Technologies. 8x Microsoft MVP. Book Author. Husband. Father of 7. Armchair Philosopher.