Command Palette

Search for a command to run...

Hectal
PHASE 5Beginner ~24 min· topic 4 of 10

Topic 5.4

map and filter

In one line

map makes a new array by changing every item with a function you give it, and filter makes a new array keeping only the items your function says yes to; neither changes the original, and they chain together into clear data pipelines.

Think of it like this

A juice stall. map is the juicer: put in 5 oranges, get out 5 glasses of juice, one for each orange, every one changed the same way. filter is the person sorting fruit: put in 5 oranges, get out only the good ones (maybe 3), each one untouched. You can line them up: sort first, then juice what's left.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

map
Makes a new array of the same length by transforming every item.
filter
Makes a new array containing only the items that pass a test.
Transform
Turn one value into another, for example a price into a price with tax.
Truthy
A value that counts as true in a condition: everything except false, 0, "", null, undefined and NaN.
Chaining
Calling one method on the result of another: arr.filter(...).map(...).
Implicit return
An arrow function without curly braces returns its expression automatically: (n) => n * 2.
Pure callback
A callback that only uses its inputs and returns a value, without changing anything outside it.

Step by step

01map: one in, one out

map hands each element to your function and collects what comes back.

app.jswhole filejavascript
const prices = [100, 250, 40];
const half = prices.map((p) => p / 2);
// half   is [ 50, 125, 20 ]
// prices is still [ 100, 250, 40 ]

02filter: keep or drop

Return true to keep an item, false to drop it.

app.jswhole filejavascript
const cheap = prices.filter((p) => p < 200);
// cheap is [ 100, 40 ]

03See the shapes

map keeps the length and changes the values. filter keeps the values and changes the length.

See the shapesdiagram
Rendering diagram…

04Chain them

Each step gets the array the previous step returned. Put each call on its own line so it reads like a list of steps.

app.jswhole filejavascript
const names = products
  .filter((p) => p.inStock)
  .map((p) => p.name);

05Remember to return

Short arrow: the value is returned for you. Arrow with { }: write return. Returning an object from a short arrow: wrap it in ( ).

app.jswhole filejavascript
nums.map((n) => n * 2);             // ok
nums.map((n) => { return n * 2; }); // ok
nums.map((n) => { n * 2; });        // [ undefined, ... ]
nums.map((n) => ({ value: n }));    // objects

Try it yourself

  1. 1

    Add tax

    In the first example, make withTax by adding ₹10 to every price. Predict the array, then log it next to prices to prove prices didn't change.

  2. 2

    Filter by price

    In the second example, change the filter so the checkbox shows only products under ₹100. Predict which names the page will list.

  3. 3

    Fix parseInt

    In the third example, change .map(parseInt) to .map((s) => parseInt(s, 10)). Predict the output first.

Code & diagrams

map and filter on numbers New tab

`map` always gives 3 items back for 3 items in. `filter` gives back anywhere from 0 to 3.

Console

(nothing was printed)

Expected console output

[ 100, 250, 40 ] [ 50, 125, 20 ]
[ 100, 40 ]
[ '0: ₹100', '1: ₹250', '2: ₹40' ]
[ true, true, false ]
[]
A product list: filter, then map to the page New tab

Tick the box: `filter` decides what's shown, `map` turns each product into an `<li>`. The `products` array itself never changes.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

[ 'PEN', 'BAG' ]
3
The classic map and filter traps New tab

`map(parseInt)` passes the index as `parseInt`'s second argument (the radix). Braces without `return` give `undefined`. `filter(Boolean)` drops every falsy value. `map` skips holes and keeps them.

Console

(nothing was printed)

Expected console output

[ 1, NaN, NaN ]
[ 1, 2, 3 ]
[ undefined, undefined, undefined ]
[ { value: 1 }, { value: 2 }, { value: 3 } ]
[ 'hi', 5 ]
[ 10, <empty>, 30 ]

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Curly braces without return

In the first example, write prices.map((p) => { p / 2; }).

terminal
$ Console
── what you'll see ──
[ undefined, undefined, undefined ]

Break #2

Calling map on a NodeList

Write document.querySelectorAll("li").map((li) => li.textContent).

terminal
$ Console
── what you'll see ──
Uncaught TypeError: document.querySelectorAll(...).map is not a function

Myth vs fact

Myth

map changes the original array.

Fact

map always returns a new array and leaves the original alone. (Your callback could still change the objects inside it, which is why callbacks should be pure.)

Myth

filter returns undefined or null when nothing matches.

Fact

It returns an empty array []. You can safely call .length or .map on the result.

Myth

map and forEach are interchangeable.

Fact

map builds and returns a new array; forEach returns undefined. Using map only for side effects wastes an array and confuses readers.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Every map/filter in a chain creates a new intermediate array. For a few thousand items that's nothing. For millions of items in a hot loop, a single for loop or reduce, or iterator helpers (arr.values().filter(...).map(...), standardised in ES2025 and available in current browsers), avoids the intermediate arrays.

  • ▸

    map reads length once at the start; items you push to the array during the call aren't visited. Holes are skipped by map, filter and forEach, but map keeps the hole in its result, while filter drops it.

  • ▸

    map builds its result with the array's species constructor, so subclasses of Array get subclass instances back. Typed arrays' map returns the same typed array kind, so new Uint8Array([200]).map((n) => n * 2) gives 144 (wrapped around at 256).

  • ▸

    In React and similar libraries, items.map((item) => <li key={item.id}>…</li>) is how lists are drawn. The key should be a stable id from the data, not the map index, or items get mixed up when the list is reordered. The React course (/react, Topic 3.3) shows that bug live.

Remember this

  1. 1

    arr.map(fn) calls fn once for every element, in order, and builds a new array from what fn returns. The new array always has the same length as the original. [1, 2, 3].map((n) => n * 10) gives [10, 20, 30]. The original array is not changed. Use map whenever you think "turn each X into a Y": numbers into prices, objects into names, data into list items.

  2. 2

    arr.filter(fn) calls fn for every element and builds a new array of the elements for which fn returned a truthy value (Phase 2: anything that isn't false, 0, "", null, undefined or NaN). The kept elements are the same values, not copies, and keep their order. The result can be shorter, and is [] if nothing passes; it's never undefined.

  3. 3

    Both pass three arguments to your callback: (element, index, array). Most of the time you only use the first. The index is useful for numbering: names.map((name, i) => (i + 1) + ". " + name).

  4. 4

    Because both return arrays, you can chain them: products.filter((p) => p.inStock).map((p) => p.name). Read it top to bottom like a recipe: "take the products, keep the ones in stock, turn each into its name". Filter first when you can, so map does less work.

  5. 5

    The most common bug with map is forgetting to return. An arrow function with curly braces { } needs an explicit return; without it, it returns undefined, and you get an array full of undefined. To return an object literal from a short arrow, wrap it in parentheses: (n) => ({ value: n }), because bare { starts a function body.

  6. 6

    Use map only when you want the new array. If you only want to do something for each item (log it, change the page) and ignore the result, use forEach or for...of (Topic 5.7). And keep the callbacks pure (Phase 3): don't push to outside arrays or change the items inside a map; return new values instead.

Explain it without notes

01

What is the difference between map and filter?

02

Why does ["1", "2", "3"].map(parseInt) return [1, NaN, NaN]?

03

Why should you filter before mapping when you can?

04

When should you not use map?

Practice

01

Given const names = ["asha", "ravi"];, make ["Asha", "Ravi"] with map.

02

Given an array of { title, done } tasks, print the titles of the tasks that are not done.

03

Make an array of the even numbers from [1, 2, 3, 4, 5, 6], each squared.

Trade-offs

  • ↔

    Chains of filter and map are clear and easy to change; a single hand-written loop is a little faster and uses less memory for huge arrays, but is harder to read.

  • ↔

    filter(Boolean) is a neat way to drop empty values, but it also drops 0 and "", which may be real data. Write the test explicitly ((x) => x != null) when those values matter.

Done when you can

  • I can transform every item of an array with map.

  • I can keep only matching items with filter.

  • I can chain filter and map and read the chain step by step.

  • I remember to return in arrow functions with braces, and wrap object literals in ( ).

  • I can explain the map(parseInt) trap.