In our Jurassic Park we collect a mountain of data about the dinosaurs - their species, measurements, feeding habits, aggression levels and much more. Once all of that lands in an array, we hardly ever care about a single entry: we want to visit every element, reshape them, pull out the ones that matter, or boil the whole herd down to one number for the control room. JavaScript ships a family of array iteration methods built for exactly that, and each of them has a job of its own.
The
forEach method runs a function once for every element of an array. It is the most basic way of iterating - we reach for it when we want to do something with each element, such as printing it or sending it to the monitoring station, and we do not need a new array in return.1// An array of objects representing the dinosaurs in our park
2const dinosaurs = [
3 { name: "Tyrannosaurus", diet: "carnivore", aggressionLevel: 10 },
4 { name: "Triceratops", diet: "herbivore", aggressionLevel: 4 },
5 { name: "Velociraptor", diet: "carnivore", aggressionLevel: 9 },
6 { name: "Stegosaurus", diet: "herbivore", aggressionLevel: 2 },
7 { name: "Brachiosaurus", diet: "herbivore", aggressionLevel: 1 }
8];
9
10// We use forEach to print the names of all the dinosaurs
11console.log("List of dinosaurs in the park:");
12dinosaurs.forEach(function(dino) {
13 console.log(dino.name);
14});
15
16// We can also use an arrow function for a more concise notation
17console.log("List of dinosaurs with their aggression level:");
18dinosaurs.forEach(dino => {
19 console.log(`${dino.name}: Aggression level ${dino.aggressionLevel}/10`);
20});The
forEach method hands nothing back - to be exact, it returns undefined - so we use it when we only want to perform an action on the elements, without building a new array. It is worth knowing what it cannot do either: you cannot stop it halfway through. A return inside the callback ends that one call and the walk moves straight on to the next dinosaur, and break is not even allowed in there. When you need to bail out early, that is a job for find, some, every or a classic for loop.The
map method creates a new array containing the results of calling a provided function on every element of the original array. It is remarkably useful whenever we need to convert data from one shape into another - turning full dinosaur records into a plain list of names, or into short safety summaries the wardens can actually read.1// We use map to build a new array containing only the dinosaur names
2const dinoNames = dinosaurs.map(dino => dino.name);
3console.log("Dinosaur names:", dinoNames);
4// Result: ["Tyrannosaurus", "Triceratops", "Velociraptor", "Stegosaurus", "Brachiosaurus"]
5
6// We can also build more complex transformations
7const dinoSummaries = dinosaurs.map(dino => {
8 return {
9 name: dino.name,
10 dangerLevel: dino.diet === "carnivore" ? "High" : "Low",
11 warning: dino.aggressionLevel > 7 ? "Handle with extreme caution!" : "Standard safety procedures"
12 };
13});
14
15console.log("Dinosaur safety summary:", dinoSummaries);Unlike
forEach, the map method always returns a new array of the same length as the original, holding the transformed elements. That is exactly the key difference between the two: map() returns a new array, while forEach() returns undefined. It is not a question of speed - map() is not faster, both methods visit every element exactly once - and they certainly do not work identically. Neither of them can be stopped mid-run, so that is no difference between them either. Reach for map when you want the result, and for forEach when you only care about the side effect.The
filter method creates a new array containing only the elements that satisfy a given condition. It is the perfect tool when we need to pull a subset out of a bigger collection - every carnivore, every animal due for a health check, every enclosure in the eastern sector.1// We filter to get only the carnivorous dinosaurs
2const carnivores = dinosaurs.filter(dino => dino.diet === "carnivore");
3console.log("Carnivorous dinosaurs:", carnivores);
4
5// We can combine conditions to build more complex filters
6const dangerousDinos = dinosaurs.filter(dino => {
7 return dino.diet === "carnivore" && dino.aggressionLevel > 7;
8});
9console.log("The most dangerous dinosaurs:", dangerousDinos);
10
11// We can also combine filter with map, to narrow down first and transform afterwards
12const safetyWarnings = dinosaurs
13 .filter(dino => dino.aggressionLevel > 5)
14 .map(dino => `WARNING: ${dino.name} has a high aggression level!`);
15
16console.log("Safety warnings:", safetyWarnings);The
filter method returns a new array which may be shorter than the original, holding only the elements that made it through the "filter" - the ones for which the test function returned true. Notice that the dinosaurs array itself is left completely untouched: just like map, filter never edits the collection it was called on, it only reports on it.The
reduce method is the most powerful of the iteration methods, and also the most involved. It lets us "reduce" an array to a single value by running a function over every element while carrying the result of the previous step along with us.1// We calculate the sum of the aggression levels of all the dinosaurs
2const totalAggressionLevel = dinosaurs.reduce((total, dino) => {
3 return total + dino.aggressionLevel;
4}, 0); // 0 is the initial value
5
6console.log("Total aggression level in the park:", totalAggressionLevel);
7
8// We can use reduce to build richer structures, such as an object grouping dinosaurs by diet
9const dinosByDiet = dinosaurs.reduce((groups, dino) => {
10 // If the group for this diet does not exist yet, we create it
11 if (!groups[dino.diet]) {
12 groups[dino.diet] = [];
13 }
14
15 // We add the dinosaur to the matching group
16 groups[dino.diet].push(dino.name);
17
18 return groups;
19}, {}); // {} is the initial value (an empty object)
20
21console.log("Dinosaurs grouped by diet:", dinosByDiet);
22// Result: { "carnivore": ["Tyrannosaurus", "Velociraptor"], "herbivore": ["Triceratops", "Stegosaurus", "Brachiosaurus"] }So what is the
reduce() method on an array used for? It reduces an array to a single value - a sum, an average, the heaviest specimen, or one grouped object like the one above. The name misleads a lot of people, so let us be precise about what it does not do: it does not remove elements from an array, it does not reduce the size of the array (our dinosaurs array still holds five records afterwards), and it has nothing to do with sorting in descending order - that job belongs to sort.The
reduce method takes two parameters:The
find method returns the first element of an array that satisfies a given condition. When the park systems need one specific record rather than a whole list, this is the method to reach for.1// We look for a dinosaur with a given name
2const stegosaurus = dinosaurs.find(dino => dino.name === "Stegosaurus");
3console.log("Stegosaurus information:", stegosaurus);
4
5// We look for the first carnivore with an aggression level above 8
6const dangerousCarnivore = dinosaurs.find(dino => {
7 return dino.diet === "carnivore" && dino.aggressionLevel > 8;
8});
9console.log("First dangerous carnivore:", dangerousCarnivore); // TyrannosaurusUnlike
filter, which returns an array of every matching element, find returns only the first match, and when nothing matches it returns undefined. It also stops the moment it gets a hit, so the remaining dinosaurs are never examined. Its close twin findIndex behaves the same way but gives you the position of that first match instead of the element itself, and returns -1 when there is no match at all.The
some and every methods return a boolean value and are useful for checking whether the elements of an array meet certain criteria. Neither of them builds a new array - they answer a yes-or-no question about the whole collection.The
some method checks whether at least one element of the array satisfies the given condition.1// We check whether there are any dangerous dinosaurs in the park
2const hasHighAggressionDinos = dinosaurs.some(dino => dino.aggressionLevel > 8);
3console.log("Does the park have high aggression dinosaurs?", hasHighAggressionDinos); // true
4
5// We check whether there are any omnivorous dinosaurs in the park
6const hasOmnivores = dinosaurs.some(dino => dino.diet === "omnivore");
7console.log("Does the park have omnivorous dinosaurs?", hasOmnivores); // falseThe
some method short-circuits: it stops as soon as it finds the first element that passes the test, because it already knows the answer is true and looking at the rest of the herd would change nothing.The
every method checks whether all the elements of the array satisfy the given condition.1// We check whether every dinosaur has an aggression level assigned
2const allHaveAggressionLevel = dinosaurs.every(dino => dino.aggressionLevel !== undefined);
3console.log("Do all the dinosaurs have an aggression level?", allHaveAggressionLevel); // true
4
5// We check whether all the dinosaurs are herbivores
6const allHerbivores = dinosaurs.every(dino => dino.diet === "herbivore");
7console.log("Are all the dinosaurs herbivores?", allHerbivores); // falseThe
every method short-circuits the other way round: it stops at the first element that fails the test, because a single counterexample is enough to answer false. Read out loud, the pair sounds almost like plain English - "is there some dangerous dinosaur?" and "is every dinosaur fed?".The
sort method orders the elements of an array and returns the sorted array. By default it compares elements as strings, which can lead to unexpected results when sorting numbers - by default [10, 9, 100] comes out as [10, 100, 9]. That is why we usually pass it a comparison function.1// We sort the dinosaurs by name (alphabetically)
2const dinosSortedByName = [...dinosaurs].sort((a, b) => {
3 return a.name.localeCompare(b.name);
4});
5console.log("Dinosaurs sorted alphabetically:", dinosSortedByName);
6
7// We sort the dinosaurs by aggression level (from the least to the most aggressive)
8const dinosSortedByAggression = [...dinosaurs].sort((a, b) => {
9 return a.aggressionLevel - b.aggressionLevel;
10});
11console.log("Dinosaurs sorted by aggression (ascending):", dinosSortedByAggression);
12
13// We sort the dinosaurs by aggression level (from the most to the least aggressive)
14const mostDangerousFirst = [...dinosaurs].sort((a, b) => {
15 return b.aggressionLevel - a.aggressionLevel;
16});
17console.log("Dinosaurs sorted by aggression (descending):", mostDangerousFirst);Notice that we use the spread operator (
...) to create a copy of the array before sorting, because sort modifies the original array and we want to preserve the original order. The comparison function decides the direction: a negative result puts a first, which is why a.aggressionLevel - b.aggressionLevel sorts ascending, and flipping the two operands sorts descending.One of the most powerful features of these methods is that we can join them into "chains" and carry out complex operations on our data in a single readable expression. It works because
filter, map and sort all give back an array, so the next method can be called straight on the result.1// Let us find the names of all the herbivorous dinosaurs, sorted alphabetically
2const sortedHerbivoreNames = dinosaurs
3 .filter(dino => dino.diet === "herbivore") // First we filter
4 .map(dino => dino.name) // Then we transform into an array of names
5 .sort(); // Finally we sort alphabetically
6
7console.log("Herbivores (alphabetically):", sortedHerbivoreNames);
8// Result: ["Brachiosaurus", "Stegosaurus", "Triceratops"]
9
10// Let us calculate the average aggression level of the carnivores
11const averageCarnivoreAggression = dinosaurs
12 .filter(dino => dino.diet === "carnivore") // First we keep only the carnivores
13 .reduce((sum, dino, index, array) => { // Then we reduce it down to an average
14 sum += dino.aggressionLevel; // We add the aggression level to the sum
15
16 // If this is the last element, we divide by the number of elements
17 if (index === array.length - 1) {
18 return sum / array.length;
19 }
20
21 return sum; // We return the partial sum
22 }, 0);
23
24console.log("Average carnivore aggression level:", averageCarnivoreAggression); // 9.5Read a chain from left to right and the order of its links is always the same: the array comes first, then the method that narrows it down, then the method that transforms whatever survived. The array is the receiver, so
dinosaurs always opens the chain, then comes .filter(...), and only then .map(...).1// If every record carried a ready dangerous flag, the chain would read like a sentence
2const dangerousNames = dinosaurs
3 .filter(d => d.dangerous)
4 .map(d => d.name);
5
6// Our records store a diet string instead, so the test is a little longer -
7// but the order of the links in the chain never changes
8const carnivoreNames = dinosaurs
9 .filter(d => d.diet === "carnivore")
10 .map(d => d.name);
11
12console.log("Carnivore names:", carnivoreNames);Swapping those two links around would break everything: after
.map(d => d.name) we are holding an array of plain strings, and a string has no dangerous property left to filter on. Filter first, map second - that is the order worth memorising.The array iteration methods in JavaScript are an invaluable tool when working with collections of data. Each of them has its own speciality:
In our Jurassic Park these methods are indispensable for managing dinosaur data efficiently, monitoring safety and making decisions based on data. As Dr. Rex puts it, iteration methods are the park's morning briefing:
forEach walks the daily checklist, filter picks out only the animals that need attention, map turns them into a report, and reduce gives the control room a single number to act on.