DEV Community

Cover image for You don't need `forEach()`
Read the Manual
Read the Manual

Posted on • Edited on

You don't need `forEach()`

The most basic way to iterate over an array is the forEach method. However often times there is a better method for the job. Today we will take a look at common use cases and look at alternative array methods for reaching our goals.

Using forEach to solve problems often involves:

  • declaring a variable outside of the loop
  • modifying that variable inside the loop

When reading the code, this makes the code more difficult to grasp at first glance as you have to set that initial value carefully and then track where that variable is modified. Using specific array methods allows for shorter code that is more expressive and easier to understand and maintain.

Too Lazy to Read?

If you prefer to watch a video, you can watch here as I go through all these examples in the browser console showing the outputs.

Creating New Arrays with map

Sometimes you need to iterate over an array and create a new array that is based off of the original array. Maybe given a list of people, you need just a list of the full names.

Let us consider this array of the first six presidents of the United States of America.

const presidents = [
  {
    firstName: "George",
    lastName: "Washington",
    party: "Independent",
  },
  {
    firstName: "John",
    lastName: "Adams",
    party: "Federalist",
  },
  {
    firstName: "Thomas",
    lastName: "Jefferson",
    party: "Democratic-Republican",
  },
  {
    firstName: "James",
    lastName: "Madison",
    party: "Democratic-Republican",
  },
  {
    firstName: "James",
    lastName: "Monroe",
    party: "Democratic-Republican",
  },
];
Enter fullscreen mode Exit fullscreen mode

Solving this with forEach looks like so:

const names = [];
presidents.forEach((president) => {
  names.push(`${president.firstName} ${president.lastName}`);
});
Enter fullscreen mode Exit fullscreen mode

Using map, we can accomplish the same thing:

const names = presidents.map(
  (president) => `${president.firstName} ${president.lastName}`,
);
Enter fullscreen mode Exit fullscreen mode

Filtering Arrays with filter

Another use case is to remove entries that do not meet a certain criteria. From our list of presidents, let's say you only want to see those in the "Democratic-Republican" party.

Solving this with forEach looks like:

const drPresidents = [];
presidents.forEach((president) => {
  if (president.party === "Democratic-Republican") {
    drPresidents.push(president);
  }
});
Enter fullscreen mode Exit fullscreen mode

Using filter, we can accomplish the same thing:

const drPresidents = presidents.filter(
  (president) => president.party === "Democratic-Republican",
);
Enter fullscreen mode Exit fullscreen mode

Filtering and Mapping

Another use case is to create a new array from only some of the original elements. Lets continue to consider the list of presidents. Say we wanted a list of the first and last names of presidents in the "Democratic-Republican" party.

Solving this with forEach looks like:

const drPresidents = [];
presidents.forEach((president) => {
  if (president.party === "Democratic-Republican") {
    drPresidents.push(`${president.firstName} ${president.lastName}`);
  }
});
Enter fullscreen mode Exit fullscreen mode

We can combine the two methods we just reviewed, map and filter to accomplish the same thing:

const drPresidents = presidents
  .filter(president => president.party === "Democratic-Republican");
  .map(president => `${president.firstName} ${president.lastName}`)
Enter fullscreen mode Exit fullscreen mode

This will iterate over the list twice though, which may not be desirable if you are working with large data sets.
If you want to only go over the array once, you can used reduce to accomplish the same thing.

const drPresidents = presidents.reduce((acc, president) => {
  if (president.party === "Democratic-Republican") {
    acc.push(`${president.firstName} ${president.lastName}`);
  }
  return acc;
}, []);
Enter fullscreen mode Exit fullscreen mode

Alternatively you can also use flatMap to get the same result.

const drPresidents = presidents.flatMap((president) =>
  president.party === "Democratic-Republican"
    ? `${president.firstName} ${president.lastName}`
    : [],
);
Enter fullscreen mode Exit fullscreen mode

This works because flatMap will "flatten" the empty array out of the final result.

Grouping by a Property

Another common use case is to group an array by some property. If we consider our array of presidents, maybe we want to group the presidents by their party. We want an object where the keys are the party names, and the values are the presidents in that party. A final result of:

const presidentsByParty = {
  Independent: [
    {
      firstName: "George",
      lastName: "Washington",
      party: "Independent",
    },
  ],
  Federalist: [
    {
      firstName: "John",
      lastName: "Adams",
      party: "Federalist",
    },
  ],
  "Democratic-Republican": [
    {
      firstName: "Thomas",
      lastName: "Jefferson",
      party: "Democratic-Republican",
    },
    {
      firstName: "James",
      lastName: "Madison",
      party: "Democratic-Republican",
    },
    {
      firstName: "James",
      lastName: "Monroe",
      party: "Democratic-Republican",
    },
  ],
};
Enter fullscreen mode Exit fullscreen mode

We can achieve this using the forEach method like so:

const presidentsByParty = {};
presidents.forEach((president) => {
  if (!presidentsByParty[president.party]) {
    presidentsByParty[president.party] = [];
  }
  presidentsByParty[president.party].push(president);
});
Enter fullscreen mode Exit fullscreen mode

Avoiding forEach you can use reduce to achieve the same thing.

const presidentsByParty = presidents.reduce((acc, president) => {
  if (!acc[president.party]) {
    acc[president.party] = [];
  }
  acc[president.party].push(president);
  return acc;
}, {});
Enter fullscreen mode Exit fullscreen mode

Alternatively you can use the Object.groupBy method to do this.

const presidentsByParty = Object.groupBy(
  presidents,
  (president) => president.party,
);
Enter fullscreen mode Exit fullscreen mode

Searching Simple Data Types

Given an array with simple data types (strings or numbers) you might want to see if a certain value is in the array.

const scores = [99, 92, 40, 47, 83, 100, 82];

let hasPerfectScore = false;
scores.forEach((score) => {
  if (score === 100) {
    hasPerfectScore = true;
  }
});
Enter fullscreen mode Exit fullscreen mode

This will iterate over every single element in the array, even after it finds a match.

We can use the .includes method to do the same and it will stop after finding a match.

const scores = [99, 92, 40, 47, 83, 100, 82];

const hasPerfectScore = scores.includes(100);
Enter fullscreen mode Exit fullscreen mode

Searching Objects

When you use includes, you can only use it with simple data types, string and number being the most common. If you want to compare objects, you will have to use the some method.
First lets examine how you would do this with the forEach method.

const students = [
  { name: "Adam", score: 99 },
  { name: "Bryan", score: 92 },
  { name: "Calvin", score: 40 },
  { name: "Douglas", score: 47 },
  { name: "Edward", score: 83 },
  { name: "Fred", score: 100 },
  { name: "Georg", score: 82 },
];

let hasPerfectScore = false;
students.forEach((student) => {
  if (student.score === 100) {
    hasPerfectScore = true;
  }
});
Enter fullscreen mode Exit fullscreen mode

Using the some method we have:

const hasPerfectScore = students.some((student) => student.score === 100);
Enter fullscreen mode Exit fullscreen mode

Checking Every Element

Another use case is checking that all elements meet a certain criteria. For example, you might want to know if every student passed the test. Using forEach, you would have:

const MINIMUM_PASSING_SCORE = 60;

let didEveryStudentPass = true;
students.forEach((student) => {
  if (student.score < MINIMUM_PASSING_SCORE) {
    didEveryStudentPass = false;
  }
});
Enter fullscreen mode Exit fullscreen mode

As always, forEach will go over every element in the array, even after it finds a case where a student did not pass.

Here we can use the every method which will stop as soon as one of the elements does not meet the criteria.

const MINIMUM_PASSING_SCORE = 60;

const didEveryStudentPass = students.every(
  (student) => student.score >= MINIMUM_PASSING_SCORE,
);
Enter fullscreen mode Exit fullscreen mode

Contrasting every and some

You can accomplish the same thing using either method by negating the result and negating the condition. For example, the two following are equivalent.

const MINIMUM_PASSING_SCORE = 60;

const didEveryStudentPassEvery = students.every(
  (student) => student.score >= MINIMUM_PASSING_SCORE,
);
const didEveryStudentPassSome = !students.some(
  (student) => student.score < MINIMUM_PASSING_SCORE,
);
Enter fullscreen mode Exit fullscreen mode

Using some in the above, can be expressed long-hand form with the following, which is more intuitive to understand.

const didSomeStudentFail = students.some(
  (student) => student.score < MINIMUM_PASSING_SCORE,
);
const didEveryStudentPass = !didSomeStudentFail;
Enter fullscreen mode Exit fullscreen mode

However, using double negatives is less intuitive and harder to reason about. Using the simpler variation would be preferred.

Summary

So, though you can use forEach to accomplish all these use cases, there are more specific functions that can get the job done as well. There are two benefits to consider to the alternative methods.

  1. For use cases where you are searching, using the methods some, every, find and includes will be faster as they stop as soon as they find a result.
  2. More importantly however is that the intention of the code is more obvious at first glance. You have to study the code less to understand what the goal of it is, which makes maintaining the code a lot easier.

I hope this was helpful and that you learned something new that you can use in your projects!

Do you still think you need to use forEach? Let me know in the comments!

Top comments (0)