TL;DR Prefer map
and filter
over forEach
when you need to copy an array or part of it to a new one.
One of the best parts for me in the consulting line of work is that I get to see countless projects. These projects vary widely in size, the programming languages used and in developer competence. While there are multiple patterns that I feel should be abandoned, there is a clear winner in the JavaScript world: forEach to create new arrays. The pattern is actually really simple and looks something like this:
const kids = [];
people.forEach(person => {
if (person.age < 15) {
kids.push({ id: person.id, name: person.name });
}
});
What is happening here is that the array for all people is processed to find out everyone aged less than 15. Each of these ‘kids’ is then copied to the kids array by selecting a few fields of the person object.
While this works, it is a very imperative (see Programming paradigms) way to code. So what is wrong? you might be wondering. To understand this, let’s first familiarize ourselves with the two friends map
and filter
.
map
& filter
map
and filter
were introduced to JavaScript already quite a while ago as part of EcmaScript 5.1 in 2011. They are methods of arrays that allow a more functional style coding in JavaScript. As usual in the functional programming world, neither of the methods is mutating the original array. Rather they both return a new array. They both accept a single parameter that is of type function. This function is then called on each item in the original array to produce the resulting array. Let’s see what the methods do:
map
: the result of the function called for each item is placed in the new array returned by the method.filter
: the result of the function called for each item determines whether the item should be included in the array returned by the method.
They also have a third friend in the same gang but it is a little less used. This friend’s name is reduce
.
Here are simple examples to see them in action:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(number => number * 2); // [2, 4, 6, 8, 10]
const even = numbers.filter(number => number % 2 === 0); // [2, 4]
Now that we know what map
and filter
do, let’s next see an example of how I would prefer the earlier example to be written:
const kids = people
.filter(person => person.age < 15)
.map(person => ({ id: person.id, name: person.name }));
In case you are wondering about the syntax of lambda used in map
, see this Stack Overflow answer for an explanation.
So what exactly is better with this implementation:
- Separation of concerns: Filtering and changing the format of the data are two separate concerns and using a separate method for both allows separating the concerns.
- Testability: Having a simple, pure function for both purposes can easily be unit tested for various behaviours. It is worth noting that the initial implementation is not as pure as it relies on some state outside of its scope (
kids
array). - Readability: As the methods have clear purposes to either filter out data or change the format of the data, it is easy to see what kind of manipulations are being done. Especially as there are those functions of the same category like
reduce
. - Asynchronous programming:
forEach
andasync
/await
don’t play nicely together.map
on the other hand provides a useful pattern with promises andasync
/await
. More about this in the next blog post.
It is also worth noting here that map
is not to be used when you want to cause side effects (e.g. mutate global state). Especially in a case where the return value of the map
method is not even used or stored.
Conclusions
Usage of map
and filter
provides many benefits such as the separation of concerns, testability, readability and support for asynchronous programming. Thus, it is a no-brainer for me. Yet, I constantly encounter developers using forEach
. While functional programming might be a little scary, there is nothing to be afraid of with these methods even though they have some traits from that world. map
and filter
are also heavily used in reactive programming that is used nowadays more and more in the JavaScript world too thanks to RxJS. So next time you are about to write a forEach
first think about the alternative approaches. But be warned, they might change the way you code permanently.