Map, Reduce, Filter - Better Explanations?

In the end, the utilities methods for Arrays are not particularly complicated (and I don’t mean that with any condescension at all).

Just keep a few things in mind: they’re helper functions for performing common tasks. They usually iterate over every element in the array. Each utility method takes a callback function that is executed against each element in the array.

So, you’re essentially telling the array … look at each element and do this (the callback) with it.

What arguments the method passes into the callback function and what the method returns are how the utility methods vary. The name of the method is mean to convey useful information about what sort of operation is about to occur.

Array.forEach()
Iterate each element and execute the passed callback function. This method does not return a value/new array. Essentially, you’re simply saying I want to do something with each element, but nothing new will be returned. Basically the same as a for loop, except that you cannot break from .forEach() - it will always iterate all elements in the array.

Example: You have an array of customer objects, you need to update each object with some property value. Within forEach, you would directly modify the customer object with the new value.

customerList.forEach( function( customer ){
   customer.isGoodCustomer = true;
});

Array.map()
Iterate each element in the array - return a new array with an entry for each element in the original array. The callback function you provide should return a value for each element. This function allows you to translate (map) each element from its current value to a new value (as determined by your callback) in the returned array. If you return nothing from the callback function, the new array will have undefined for the new value (because a function always implicity returns undefined unless you specify a value to return).

Example: You have an array of customer objects, you want a list of customerIDs.

const customerIDs = customerList.map( function( customer ){ 
   return customer.id ;
});

Array.filter()
Iterate each element in the array - return a new array of filtered results. Your function should return a truthy or falsy value. If truthy, the element being dealt with in that iteration will be included in the returned array. If falsy, not.

Example: You have an array of customer objects, you want a list of those from a given state.

const state = 'CA';
const filteredCustomers = customerList.filter( function( customer ){
   return customer.shippingAddress.state === state;
});

Array.some()
Iterate each element in the array until you return a truthy value. Returns only true or false, not a new array. You supply a callback function that tests each element. This is useful for exiting iteration once any element matches a criteria set by your callback.

Example: Are some/any/at least one of these customers from California? (Not a great example, I know.)

const state = 'CA';
return customerList.some( function( customer ){
   return customer.shippingAddress.state === state;
});

Array.every()
Iterate each element in the array until you return a falsy value. Returns only true or false, not a new array. Like .some(), this is useful for finding out information about the array. In this case, whether or not all of them match the criteria defined by your callback.

Example:This could be useful in places like pre-post data validation.

const ok_to_process = customerList.every( function( customer ){
   // VALIDATION LOGIC
  return (customer.hasName && customer.hasAddress && customer.isReallyNice );
});

if ( !ok_to_process )
   return;

// CONTINUE PROCESSING
...

Array.reduce()
Iterate each element in the array - return one value for the entire array. Your callback determines how each element is handled. (This is the method I use least and always have to re-read the docs for).

Example: Find out how much a list of customers has spent, in total.

const totalPurchases = customerList.reduce( function( accumulated_value, customer ){
   return accumulated_value + customer.totalPurchases;
}, 0);

Reduce doesn’t have to be a mathematical operation. What if you wanted an object of states and an array of customers for each state?

let states = {};
states= customerList.reduce( function( states, customer ){

   const state = customer.shippingAddress.state;

   states[ state ] = states[ state ] || [];
   states[ state ].push( customer );
   return states;

}, states);

// NOW YOU SHOULD HAVE AN OBJECT WITH PROPERTY KEYS WHICH ARE STATES AND WHOSE
// VALUES ARE AN ARRAY OF CUSTOMERS WITH SHIPPING ADDRESSES FOR THOSE STATES
// YOU COULD ALSO JUST AS EASILY DO THIS WITH .forEach() ...

The fun thing is that, when you know what the utility functions return, you can start combining them in interesting ways.

Want a list of customerIDs for customers from California that purchased more that $100?

const orderThreshold = 100;
const state = 'CA';

const customerIDs = customerList.filter( function( customer ){
   return ( customer.shippingAddress.state === state && customer.totalOrders > orderThreshold );
})
.map( customer => customer.id );

// THE FILTER METHOD RETURNS AN ARRAY OF CUSTOMER OBJECTS THAT PASS THE FILTER FUNCTION
// THE MAP METHOD THEN TAKES THAT ARRAY AND RETURNS A NEW ARRAY OF CUSTOMER IDs

NOTE
All of the above examples can be done with for() loops. You don’t have to use any of the utility methods. But their use simplifies the reading of the code - when you see a .filter() method, you know that you will be getting a new array in return that matches some criteria. When you see a .map() method, you know you’ll get a new array with the same number of elements as the input array, but presumably with values that have been mapped/transformed in some fashion. When you see a `.reduce()’ method, you should expect the return value to be some form of summation/reduction of the data passed to it.

The utility methods are meant to simplify writing and assist the readability and reasoning about the code.

And that wound up a lot longer than I initially planned!

I hope it’s useful …

~Micheal

18 Likes