You don’t need `forEach()`

RMAG news

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.

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.

Solving this with forEach looks like so:

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

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

Using map, we can accomplish the same thing:

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:

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

Using filter, we can accomplish the same thing:

const democraticRepublicanPresidents = 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:

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

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

const democraticRepublicanPresidents = 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.

const democraticRepublicanPresidents = 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.

const democraticRepublicanPresidents = 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:

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:

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.

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.

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.

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.

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.

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:

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:

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.

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.

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.

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.

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

Please follow and like us:
Pin Share