Front End Collision

Blog of software engineer Josh Beam

3 reasons you should not be using Array.prototype.forEach

15 August 2015 — Josh Beam — javascript

All Posts
One of the main problems with forEach is that it primarily relies on side effects, whereas some native Array.prototype alternatives make use of semantically-correct programming paradigms (such as reduction, mapping, and filtering) and may in turn introduce less incidental complexity (and enhance readability) when writing code.

3) You should be filtering

In this example, we have an array, and we want to eliminate items from an array that don’t meet a specific criteria. Here’s how you’ll see it done with forEach:

Bad

var filteredArray = [];

[1, 2, 3, 4, 5].forEach(function(number) {
  if(number > 3) {
    filteredArray.push(number);
  }
});

console.log(filteredArray);
[4, 5]

This is a typical implementation. With forEach, you simply push each object to a completely new array. You’ll notice that this introduces extra state to maintain. More state equals more brainpower needed to understand what is happening.

Luckily, Array.prototype.filter already has you covered there. It simply returns a new array of the items we want.

Good

var filtered = [1, 2, 3, 4, 5].filter(isBig);

function isBig(number) {
  return number > 3;
}

console.log(filtered);
[4, 5]

2) You should be mapping

In this example, we want to “change” each value in an array to something else. Here’s how we’d do it with forEach:

Bad

var stringNames = [];

var names = [{
  first: 'Josh',
  last: 'Beam'
},
{
  first: 'Ozzy',
  last: 'Osbourne'
}]

names.forEach(function(name) {
  stringNames.push(name.first + ' ' + name.last);
});
["Josh Beam", "Ozzy Osbourne"]

Again, naked looping requires us to create additional state within our application.

Here, we’re gonna use map instead. Again, it simply returns a new array without requiring us to do it ourselves:

Good

var names = [{
  first: 'Josh',
  last: 'Beam'
},
{
  first: 'Ozzy',
  last: 'Osbourne'
}]

names = names.map(fullName);

function fullName(name) {
  return name.first + ' ' + name.last;
}

console.log(names);
["Josh Beam", "Ozzy Osbourne"]

1) You should be reducing

Here, we want to combine certain values in an array.

Bad

var total = 0;

[1, 2, 3, 4, 5].forEach(function(number) {
  total += number;
});

console.log(total);
15

In other words, when you think “reduce”, think about “collapsing” items into a new item. Another way to think of it is to imagine you have an array filled with the words of a sentence, and you want to concatenate them all into one string.

Good

var total = [1, 2, 3, 4, 5].reduce(addAll);

function addAll(total, current) {
  return total + current;
}

console.log(total);
15

Conclusion

You’ll notice one of the overarching concepts of all these three methods is that they all take the “functional route”, where they don’t necessarily require the manual creation of additional state. It is possible to introduce side effects within these methods, however, they don’t primarily rely on side effects to function. In other words, you’ll see that Array.prototype.forEach primarly relies on side effects. It never returns a value other than undefined unless you explicity force it to.

Why am I referring to functional programming in the context of JavaScript, which is clearly not a functional programming language? Well, to quote the omniscient Wikipedia article on functional programming:

Eliminating side effects, i.e. changes in state that do not depend on the function inputs, can make it much easier to understand and predict the behavior of a program

In other words, it can take more “brain power” to understand what is happening in a forEach loop whose sole purpose is to mutate outside data based on side effects, whereas map, reduce, and filter all have immediate and clear semantic meanings to the programmer (that’s not to say, though, that you can’t use the power of those methods for evil).