DEV Community

JP Erasmus
JP Erasmus

Posted on • Originally published at Medium on

“forEach” all the things!

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)

Collapse
 
wolfhoundjesse profile image
Jesse M. Holmes

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.