Course outline · 0% complete

0/29 lessons0%

Course overview →

map and filter

lesson 7-1 · ~10 min · 22/29

Loops, replaced by methods

Lesson 6-2 introduced the arrow function, including the rule that a braceless body is returned automatically. So n => n * 2 called with 5 gives 10, and no return keyword appears anywhere in it.

This unit is the reason arrows are worth that much attention. The three methods covered here all take a small function as their argument, and writing those functions as arrows is what keeps the resulting code down to one readable line.

Loops you do not have to write

In lesson 4-3 you wrote loops to transform and select. Arrays carry built-in methods that do those jobs in one line. Each takes a function (usually an arrow) and applies it to every item.

map transforms each item and returns a new array of the same length:

const nums = [1, 2, 3, 4, 5];
const doubled = nums.map(n => n * 2);   // [2, 4, 6, 8, 10]

filter keeps the items where your arrow answers true, returning a new, possibly shorter array:

const evens = nums.filter(n => n % 2 === 0);   // [2, 4]

Neither method touches the original array. And because each returns an array, you can chain them:

prices.filter(p => p >= 10).map(p => p / 2)

Python's list comprehensions covered both jobs: [n * 2 for n in nums] is a map, and [n for n in nums if ...] is a filter.

[1..5]input arraymapn => n * 2filtereven only3each value passes through the chain, original array untouched
A value traveling through a map then filter chain. Every item makes the same trip, and each stage returns a fresh array.

Transforming, selecting, and both at once

Three uses of the two methods: map alone, filter alone, and a chain that does one after the other.

const nums = [1, 2, 3, 4, 5];

const doubled = nums.map(n => n * 2);
console.log(doubled);

const evens = nums.filter(n => n % 2 === 0);
console.log(evens);

const prices = [5, 12, 8, 30];
const sale = prices.filter(p => p >= 10).map(p => p / 2);
console.log(sale);

Output

[ 2, 4, 6, 8, 10 ]
[ 2, 4 ]
[ 6, 15 ]

The lengths tell the story. map returned five items for five inputs, because it transforms and never selects. filter returned two, because it selects and never transforms.

The chained line reads left to right as a pipeline. prices.filter(p => p >= 10) narrows [5, 12, 8, 30] down to [12, 30], and .map(p => p / 2) then halves those survivors to give [6, 15]. Chaining works because each method hands back an array, which is something the next method can be called on.

Mapping a whole array through a formula

The Celsius conversion from lesson 6-1 applied to an entire array. The formula is identical, and the only new part is handing it to map as an arrow.

const temps = [0, 25, 100];

const f = temps.map(c => c * 9 / 5 + 32);
console.log(f);

Output

[ 32, 77, 212 ]

The arrow takes one temperature at a time, under the name c, and map takes care of calling it once per item and collecting the results in order. Nothing in the arrow knows that an array is involved, which is exactly the division of labor from the callback lesson: map owns the repetition and your arrow owns the calculation.

temps itself still holds [0, 25, 100] afterwards, since map builds a new array rather than editing the old one.

Chaining over an array of objects

Real data is usually objects, and the order of the two methods matters. Filtering comes first, while whole objects are still available to test, and mapping comes second to pull out the one field that is wanted.

const students = [
  { name: "Ada", score: 91 },
  { name: "Alan", score: 58 },
  { name: "Grace", score: 74 }
];

const passingNames = students.filter(s => s.score >= 70).map(s => s.name);
console.log(passingNames);

Output

[ 'Ada', 'Grace' ]

The arrow s => s.score >= 70 tests each student object and keeps Ada and Grace, and s => s.name then reduces each surviving object down to its name string.

Reversing the two stages would break the code rather than just reorder it. After .map(s => s.name) the array holds plain strings, and a string has no .score property to filter on, so the test would compare undefined >= 70 and keep nothing. The habit to build is to filter while the data is still rich, then map to narrow it.

Why map never shrinks an array

For a nums array with 6 items, nums.map(n => n > 3) returns an array of 6 items, filled with true and false values.

map is one in, one out. Every item becomes exactly one result, and here the result of each arrow call is the boolean answer to the comparison rather than the number itself. The length cannot change, because there is no mechanism in map for discarding anything.

Selecting the items greater than 3 is filter's job, and mixing up the two is the classic mistake with this toolkit. The arrow you write is nearly identical in both cases, which is why the confusion happens, so it helps to name what each method does with the arrow's answer.

MethodWhat the arrow returnsResult length
mapthe replacement value for that itemalways the same as the input
filtertrue to keep the item, false to drop itthe same or shorter