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",
},
];
Solving this with forEach
looks like so:
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 drPresidents = [];
presidents.forEach((president) => {
if (president.party === "Democratic-Republican") {
drPresidents.push(president);
}
});
Using filter
, we can accomplish the same thing:
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:
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:
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.
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.
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:
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
andincludes
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!
Do you still think you need to use forEach
? Let me know in the comments!
Top comments (0)