Disclaimer: this is an opinion piece, feel free to disagree.
Having done a good number of code reviews over the past few years and seeing what comes back to bite us vs. what generally works the way we intended, one such issue is knowing when to use forEach
. It comes down to asking yourself the right questions.
On a separate but related point, I would argue that being comfortable with AND knowing when to use forEach
, map
, filter
or reduce
, you can tackle any array-related data manipulation problem, so it really pays, as a developer, putting in the effort to get very comfortable with it. Sure there are some other very useful methods, like flatMap
, but knowing these four, you can go a long way.
This post will use JavaScript as an example, but the concept is the same for any language that supports the higher-order array functions: forEach
, map
, filter
and reduce
.
Who is this post for?
Developers that already know to favour forEach
over for
loops to write more beautiful (declarative!) code, but are perhaps not comfortable with these higher-order array functions. There is a place for a for
loop, but more often than not, you should use the more declarative option.
“forEach” all the things!
The first thing developers start doing when they move from the imperative for loop to more declarative forEach functions, is that they “forEach all the things”!
This is okay and definitely better than using for loops everywhere, but we can do better.
The thinking process
Sure you can use a shoe to hang a picture on the wall, but a hammer is a better tool for the job.
Use the right tool for the job.
What follows are questions you can ask yourself while coding and trying to decide whether you should use forEach
, map
, filter
or reduce
.
1. I’ve got an array of things and I want to manipulate each item in some way
The wrong way: use forEach
const list = ['a', 'b', 'c']
const newList = []
list.forEach(item => {
newList.push(item.toUpperCase())
})
console.log(newList) // ['A', 'B', 'C']
The right way: use map
const list = ['a', 'b', 'c']
const newList = list.map(item => item.toUpperCase())
console.log(newList) // ['A', 'B', 'C']
2. I’ve got an array of things and I want to “reduce” it to one thing
This one is easy to remember once you are comfortable with it and it works really well for situations like summing totals or reducing a list of objects into a single object. You can think of reduce
as compressing a list of inputs into a single output.
The wrong way: use forEach
let total = 0
const list = [1, 2, 3, 4]
list.forEach(item => {
total = total + item
})
console.log(total) // 10
The right way: use reduce
const list = [1, 2, 3, 4]
const total = list.reduce((sum, item) => sum + item, 0)
console.log(total) // 10
This example, to sum a few numbers, doesn’t make the pain obvious, but working with more complex data will surface the problem.
3. I’ve got an array of things, but I only want a subset of them
The wrong way: use forEach (noticing a pattern here?)
const list = [1, 2, 3, 4]
const evenNumbers = []
list.forEach(item => {
if (item % 2 === 0) {
evenNumbers.push(item)
}
})
console.log(evenNumbers)
The right way: use filter
const list = [1, 2, 3, 4]
const evenNumbers = list.filter(item => item % 2 === 0)
console.log(evenNumbers)
4. I’ve got an array of things and I want to perform an action for each item but I don’t care about a return value
The wrong way: use map, filter or reduce
Just like it is technically possible to use forEach
for everything else, as we saw above, it is possible to use any of the other higher-order functions here, but it would not be the right tool for the job.
“It is just semantics” is not a thing.
const list = ['user1', 'user2', 'user3']
const newList = list.map(user => fireAndForgetNetworkRequest(user))
console.log(newList) // [undefined, undefined, undefined]
The right way: use forEach
const list = ['user1', 'user2', 'user3']
list.forEach(user => fireAndForgetNetworkRequest(user))
// nothing to log
Hint: If you can’t declare your variables with const and you need to use let, you might be using the wrong method.
TL;DR;
If you made it this far, hopefully, the post wasn’t too boring. Let me know in the comments if you disagree with anything or if you have some better examples that I haven’t thought of.
Published: 10 May 2018
Top comments (1)
I came here to post on this very topic, but I'll just ask you instead. I've seen plenty of performance tests where
.map()
outperforms.forEach()
, sometimes by a factor of 3.I already know the answer for my use case because I'm not expecting a return value, but I'm curious. Would there ever be a case where you'd use
.map()
in favor of performance, return values be damned? I think it would be an interesting conversation about semantics vs performance. After all, faster isn't really important in my use case.