JavaScript and TypeScript course Β· Module 5: Advanced JavaScript
Higher-Order Array Methods: map, filter, reduce
In this lesson7
The park vet asks for a report: the names of all carnivores, their total danger level and the average age of the herd. A plain for loop can do it, but every answer takes several lines of counters, conditions and temporary arrays. Now that you know the basic array methods, it's time to learn three of the most important higher-order methods, that is methods that take a function as an argument. They are the cornerstone of functional programming in JavaScript and incredibly useful when working with dinosaur data.
map() - Transforming Data
The map() method creates a new array by running a function on every element of the original array. It does not modify the original array. Whatever the function returns ends up in the result:
1const dinosaurs = [
2 { name: "Blue", age: 4, species: "Velociraptor" },
3 { name: "Rex", age: 12, species: "T-Rex" },
4 { name: "Bronto", age: 8, species: "Brachiosaurus" }
5];
6
7// Extract only the names
8const names = dinosaurs.map(dino => dino.name);
9console.log(names); // ["Blue", "Rex", "Bronto"]
10
11// Transform into a new format
12const reports = dinosaurs.map(dino => ({
13 label: dino.name + " (" + dino.species + ")",
14 ageInMonths: dino.age * 12
15}));
16console.log(reports);
17// [{ label: "Blue (Velociraptor)", ageInMonths: 48 }, ...]The result always has as many elements as the original: three dinosaurs in, three reports out. The parentheses around the object literal in the second example are required, because without them JavaScript would treat the braces as the body of the arrow function.
filter() - Filtering Data
The filter() method creates a new array containing only the elements that satisfy the given condition (the function returns true). It works like a gate at the enclosure:
1const dinosaurs = [
2 { name: "Blue", age: 4, diet: "carnivore", dangerLevel: 8 },
3 { name: "Rex", age: 12, diet: "carnivore", dangerLevel: 10 },
4 { name: "Bronto", age: 8, diet: "herbivore", dangerLevel: 2 },
5 { name: "Triki", age: 6, diet: "herbivore", dangerLevel: 3 }
6];
7
8// Filter carnivores
9const carnivores = dinosaurs.filter(dino => dino.diet === "carnivore");
10console.log(carnivores.length); // 2
11
12// Dinosaurs with a high danger level
13const dangerous = dinosaurs.filter(dino => dino.dangerLevel >= 8);
14console.log(dangerous.map(d => d.name)); // ["Blue", "Rex"]Only the dinosaurs for which the function returned true pass through the gate. They are not copied - these are the same objects as in the original, gathered in a new array. When nobody meets the condition, you get an empty array, not undefined.
reduce() - Aggregating Data
The reduce() method reduces an array to a single value by processing elements one by one with an accumulator. The accumulator is the lab technician's notebook, and the second argument of reduce() is its starting state:
1const dinosaurs = [
2 { name: "Blue", age: 4 },
3 { name: "Rex", age: 12 },
4 { name: "Bronto", age: 8 },
5 { name: "Triki", age: 6 }
6];
7
8// Sum of all ages
9const totalAge = dinosaurs.reduce((sum, dino) => sum + dino.age, 0);
10console.log(totalAge); // 30
11
12// Average age
13const avgAge = totalAge / dinosaurs.length;
14console.log(avgAge); // 7.5The summing goes like this: 0 + 4, then + 12, + 8 and + 6, which gives 30. Always pass the starting value: without it reduce() takes the first element as the accumulator, here the whole Blue object, and instead of a number you get glued-together text. On an empty array without a starting value you get a TypeError.
The accumulator does not have to be a number. It can be an object to which we add dinosaurs by diet - that is how grouping works:
1const dinosaurs = [
2 { name: "Blue", age: 4, diet: "carnivore" },
3 { name: "Rex", age: 12, diet: "carnivore" },
4 { name: "Bronto", age: 8, diet: "herbivore" },
5 { name: "Triki", age: 6 } // no diet recorded
6];
7
8// Grouping dinosaurs by diet
9const grouped = dinosaurs.reduce((groups, dino) => {
10 const key = dino.diet || "unknown";
11 if (!groups[key]) groups[key] = [];
12 groups[key].push(dino.name);
13 return groups;
14}, {});
15
16console.log(grouped);
17// { carnivore: ["Blue", "Rex"], herbivore: ["Bronto"], unknown: ["Triki"] }The empty object used as the starting value becomes a catalog of groups, and Triki, with no diet recorded, lands in the unknown group thanks to the || operator. Since ES2024 Object.groupBy() does the same in fewer lines - you will meet it in the lesson about the latest JavaScript features.
Chaining Methods into a Pipeline
The real power of these methods shows when we combine them into chains (chaining). Each method receives the result of the previous one, like a sample passed between lab stations:
1const dinosaurs = [
2 { name: "Blue", age: 4, diet: "carnivore", dangerLevel: 8 },
3 { name: "Rex", age: 12, diet: "carnivore", dangerLevel: 10 },
4 { name: "Bronto", age: 8, diet: "herbivore", dangerLevel: 2 },
5 { name: "Triki", age: 6, diet: "herbivore", dangerLevel: 3 }
6];
7
8// Pipeline: filter carnivores -> extract names -> sort
9const dangerousNames = dinosaurs
10 .filter(dino => dino.diet === "carnivore")
11 .map(dino => dino.name)
12 .sort();
13
14console.log(dangerousNames); // ["Blue", "Rex"]
15
16// Total danger level of carnivores
17const totalDanger = dinosaurs
18 .filter(dino => dino.diet === "carnivore")
19 .reduce((sum, dino) => sum + dino.dangerLevel, 0);
20
21console.log(totalDanger); // 18The order of the links matters: we filter first, so the later steps work on a smaller array. Watch out for sort(), because unlike the rest of the chain it mutates the array - here it is safe, since it sorts the fresh result of map(). Without a compare function it also sorts as text, so the numbers 9, 10 and 1 would end up as 1, 10, 9.
Searching and Checking
Not every question needs a new array. Sometimes you want to find one dinosaur or just check a condition - that is what the search and check methods are for:
1const names = ["Blue", "Rex", "Bronto", "Triki"];
2const levels = [8, 10, 2, 3];
3
4console.log(names.indexOf("Rex")); // 1
5console.log(names.includes("Delta")); // false
6console.log(levels.find(level => level > 5)); // 8 - the first match
7console.log(levels.some(level => level >= 10)); // true - at least one
8console.log(levels.every(level => level > 0)); // true - all of themindexOf() returns a position or -1, includes() answers yes or no, and find() returns the first matching element or undefined. some() and every() stop working as soon as they know the answer.
flat() and flatMap() - Flattening
Data from the sectors often arrives as arrays inside arrays. The flat() method from ES2019 flattens them by one level, and flatMap() combines map() with that flattening:
1const sectors = [["Blue", "Delta"], ["Rex"], ["Bronto", "Triki"]];
2console.log(sectors.flat()); // ["Blue", "Delta", "Rex", "Bronto", "Triki"]
3
4const zones = [
5 { id: "A", dinosaurs: ["Blue", "Delta"] },
6 { id: "B", dinosaurs: ["Rex"] }
7];
8const everyone = zones.flatMap(zone => zone.dinosaurs);
9console.log(everyone); // ["Blue", "Delta", "Rex"]A plain map() would return an array of arrays here, while flatMap() gives a single list of residents right away. You can flatten deeper nesting by passing a depth, for example flat(2).
Function Composition: pipe and compose
A filter() and map() chain needs an array. Plain functions are combined through composition: pipe() passes a value through successive steps from left to right, and we will build it with reduce() itself:
1const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
2
3const onlyCarnivores = list => list.filter(dino => dino.diet === "carnivore");
4const toNames = list => list.map(dino => dino.name);
5
6const carnivoreNames = pipe(onlyCarnivores, toNames);
7console.log(carnivoreNames([
8 { name: "Blue", diet: "carnivore" },
9 { name: "Bronto", diet: "herbivore" }
10])); // ["Blue"]Here the accumulator is the value itself, and each function is applied to the result of the previous one. compose() does the same from right to left, usually with reduceRight(). You will develop composition further in the location about functional programming.
This is the path to mastering array methods: basic manipulation (push, pop, shift, unshift), searching (indexOf, includes, find), transformation (map, filter, reduce), aggregation (every, some, flat), and at the top the compose and pipe patterns. In the lab below you will complete the reports following the TODO comments.
Remember: map() transforms, filter() sifts, and reduce() folds the whole herd into one result - together they form the lab's production line.
Code for this lesson: index.js
1// Higher-order array methods: map, filter, reduce
2
3const dinosaurs = [
4 { name: "Blue", age: 4, diet: "carnivore", dangerLevel: 8 },
5 { name: "Rex", age: 12, diet: "carnivore", dangerLevel: 10 },
6 { name: "Bronto", age: 8, diet: "herbivore", dangerLevel: 2 },
7 { name: "Triki", age: 6, diet: "herbivore", dangerLevel: 3 },
8 { name: "Ptero", age: 5, diet: "carnivore", dangerLevel: 6 }
9];
10
11// === MAP ===
12console.log("=== map() ===");
13const names = dinosaurs.map(dino => dino.name);
14console.log("Names:", names);
15
16// TODO: Use map() to create an array of objects
17// with the fields: label (name + species) and ageInMonths (age * 12)
18
19// === FILTER ===
20console.log("\n=== filter() ===");
21const carnivores = dinosaurs.filter(dino => dino.diet === "carnivore");
22console.log("Carnivores:", carnivores.map(d => d.name));
23
24// TODO: Filter the dinosaurs with dangerLevel >= 7
25
26// === REDUCE ===
27console.log("\n=== reduce() ===");
28const totalAge = dinosaurs.reduce((sum, dino) => sum + dino.age, 0);
29console.log("Sum of ages:", totalAge);
30console.log("Average age:", totalAge / dinosaurs.length);
31
32// TODO: Use reduce() to calculate the total danger level
33
34// === PIPELINE (method chaining) ===
35console.log("\n=== Pipeline ===");
36// TODO: Chain filter -> map -> reduce
37// Filter the carnivores, take dangerLevel, add it up
38const totalDangerOfCarnivores = dinosaurs
39 .filter(dino => dino.diet === "carnivore")
40 .map(dino => dino.dangerLevel)
41 .reduce((sum, level) => sum + level, 0);
42
43console.log("Total danger of the carnivores:", totalDangerOfCarnivores);Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What does the map() method return in JavaScript?
2. What is the purpose of the filter() method in arrays?
These are 2 of 4 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Code editor
Transform the Velociraptor array by adding a trainingComplete property to each one.
- Code editor
Filter the array to find only alpha Velociraptors.
- Click in order
Arrange the map method syntax in the correct order:
- Code editor
Calculate the average age of the pack using reduce().
- Click in order
Arrange the filter method syntax in the correct order:
- Vertical ordering
Order the array methods learning path from basic to advanced: