You don't need `forEach()`
Tags: javascript, beginners, webdev, programming
Published: 2024-06-13 02:46:19

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](https://www.youtube.com/watch?v=jG_Vq1y0lX8) 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.

```js
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",
  },
];
```

Solving this with `forEach` looks like so:

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

Using `map`, we can accomplish the same thing:

```js
const names = presidents.map(
  (president) => `${president.firstName} ${president.lastName}`,
);
```

## 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:

```js
const drPresidents = [];
presidents.forEach((president) => {
  if (president.party === "Democratic-Republican") {
    drPresidents.push(president);
  }
});
```

Using `filter`, we can accomplish the same thing:

```js
const drPresidents = presidents.filter(
  (president) => president.party === "Democratic-Republican",
);
```

## 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:

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

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

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

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.

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

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

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

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:

```js
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",
    },
  ],
};
```

We can achieve this using the `forEach` method like so:

```js
const presidentsByParty = {};
presidents.forEach((president) => {
  if (!presidentsByParty[president.party]) {
    presidentsByParty[president.party] = [];
  }
  presidentsByParty[president.party].push(president);
});
```

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

```js
const presidentsByParty = presidents.reduce((acc, president) => {
  if (!acc[president.party]) {
    acc[president.party] = [];
  }
  acc[president.party].push(president);
  return acc;
}, {});
```

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

```js
const presidentsByParty = Object.groupBy(
  presidents,
  (president) => president.party,
);
```

## 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.

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

let hasPerfectScore = false;
scores.forEach((score) => {
  if (score === 100) {
    hasPerfectScore = true;
  }
});
```

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.

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

const hasPerfectScore = scores.includes(100);
```

## 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.

```js
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;
  }
});
```

Using the `some` method we have:

```js
const hasPerfectScore = students.some((student) => student.score === 100);
```

## 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:

```js
const MINIMUM_PASSING_SCORE = 60;

let didEveryStudentPass = true;
students.forEach((student) => {
  if (student.score < MINIMUM_PASSING_SCORE) {
    didEveryStudentPass = false;
  }
});
```

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.

```js
const MINIMUM_PASSING_SCORE = 60;

const didEveryStudentPass = students.every(
  (student) => student.score >= MINIMUM_PASSING_SCORE,
);
```

## 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.

```js
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,
);
```

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

```js
const didSomeStudentFail = students.some(
  (student) => student.score < MINIMUM_PASSING_SCORE,
);
const didEveryStudentPass = !didSomeStudentFail;
```

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!