Learn by reading through in order

map and filter — Transforming vs. Narrowing Down an Array

Learn map, which transforms every element, and filter, which keeps only the ones that match. Covers why the original stays intact, chaining, and the index argument.

Whether you're pulling tax-included prices out of a menu or picking out only the products in stock, you keep writing the same code: declare an empty array, then call push on it inside forEach.

This article covers map, which transforms elements, and filter, which keeps only the elements that match a condition.

Transforming Each Element Into a New Array — map

Say you want a café menu's pre-tax prices converted to tax-included prices. The result should be an array in the same order as the original three items, with only the prices changed. You can compute each price with forEach, but to collect the results you have to create an array yourself and keep track of it until the loop ends.

The array method map (a method that passes each element through a callback and returns a new array of the return values, in order) takes care of both creating the result array and looping. The function you pass only has to describe how to transform a single element and return the result.

const menuItems = [
  { name: "House Blend", price: 480 },
  { name: "Caffe Latte", price: 540 },
  { name: "Cheesecake", price: 620 },
];

// Put just the transformation for one item in a function
const toTaxIncluded = (item) => Math.floor(item.price * 1.1);

// toTaxIncluded is called once per element, and the return values fill the new array
const taxIncluded = menuItems.map(toTaxIncluded);
console.log(taxIncluded.join(", "));   // 528, 594, 682
console.log(taxIncluded.length);       // 3
console.log(menuItems.length);         // 3
Each Returned Value Lands in the Same Position
One function:toTaxIncludedmenuItemsindex 0: 480menuItemsindex 1: 540menuItemsindex 2: 620480 × 1.1 →returns 528540 × 1.1 →returns 594620 × 1.1 →returns 682taxIncludedindex 0taxIncludedindex 1taxIncludedindex 2
The three return values go into taxIncluded in the same order as the original elements. map never changes the number of elements — only their values.

map simply hands each element to the callback one at a time and adds whatever comes back to the new array, so the number of elements can't change. Narrowing an array down, which does change that number, takes a different method.

forEach Doesn't Give You a Return Value

forEach only calls the function you pass; it doesn't collect the return values. It always returns undefined, so even if you write const list = menuItems.forEach(toTaxIncluded);, list is undefined. To get the results back as an array, use map.

Turn a bakery's products into a list of sale prices and a list of names. bakeryItems is already declared.

① Create an array salePrices of sale prices (price times 0.8, rounded down).

② Display salePrices joined with ", ".

③ Create an array of just the product names and display it joined with " / ".

④ Display the price of the first item in bakeryItems.

(If it runs correctly, an explanation will appear.)

JavaScript / TypeScript Editor

Run code to see output

Keeping Only Matching Elements — filter and the Resulting Length

Say you want to list only the books that are in stock. map never changes the number of elements, so it can't drop the out-of-stock ones, and you're back to writing an if inside forEach and calling push.

filter (a method that returns a new array of only the elements for which the callback returned true) handles this kind of narrowing. The function you pass receives an element and returns a boolean that says whether to keep it.

const books = [
  { title: "Practical SQL", stock: 0 },
  { title: "Networking Basics", stock: 5 },
  { title: "Linux Illustrated", stock: 2 },
];

// Write a function that returns a boolean: keep it or not
const isInStock = (book) => book.stock > 0;

// Only elements that returned true go into the new array
const available = books.filter(isInStock);
console.log(available.length);      // 2
console.log(available[0].title);    // Networking Basics
console.log(books.length);          // 3
The Remaining Elements Shift Forward
books index 0stock 0stock > 0 isfalseNot addedto availablebooks index 1stock 5stock > 0 istrueavailableindex 0books index 2stock 2stock > 0 istrueavailableindex 1
Only the two items that returned true go into available. The length drops by however many were left out, and the remaining elements shift forward to fill the gaps.

available only knows the new positions; the index each item had in books is lost. You'll see how to work with the original position at the end of this article. The table below sums up how each of the three methods uses the return value of the function you pass.

MethodCallback's return valueLength of the resulting array
mapGoes into the new arraySame as the original
filterIf true, the element is keptSame as the original or fewer
forEachIgnoredNo array is created (returns undefined)

Curly Braces Need a return

If you write the body in curly braces, as in (book) => { book.stock > 0 }, the return value is undefined unless you write return. undefined is falsy, so no element is kept and you get an empty array. For a one-line check, leave the curly braces off.

Split a houseplant shop's product list by pot size. plants is already declared.

① Create an array largePots with only the large-pot products, and display how many there are.

② Display the product names left in largePots, joined with ", ".

③ Create an array smallPots with only the small-pot products, and display the product names joined with ", ".

④ Display the length of plants.

JavaScript / TypeScript Editor

Run code to see output

Narrow Down, Then Transform — Chaining filter and map

Say you want to turn only the published courses into strings for display. You could store filter's result in a variable and call .map(...) on it in a second line, but then you're naming an intermediate array you'll never use again.

filter returns an array too, so you can call .map(...) directly on its return value. Linking method calls one after another like this is called chaining (calling the next method on the previous method's return value).

const courses = [
  { title: "Intro to Git", minutes: 45, published: true },
  { title: "SQL Practice Drills", minutes: 90, published: false },
  { title: "Linux Basics", minutes: 60, published: true },
];

// Keep only the published ones, then turn the result into display strings
// A leading . calls the method on the previous line's result (it's all still one expression)
const labels = courses
  .filter((course) => course.published)   // published is already a boolean, so return it as-is
  .map((course) => `${course.title} (${course.minutes} min)`);

console.log(labels.join(" / "));
// Intro to Git (45 min) / Linux Basics (60 min)
console.log(labels.length);    // 2
console.log(courses.length);   // 3
The Three Arrays in a Chain
courses3 objectsThe originalstill has 3.filter(...)2 objectsNever named.map(...)2 stringsStoredin labels
.map reads the array returned by filter without that array ever getting a name. Each step in the chain creates one new array.

To inspect the intermediate result, store filter's result in a variable, print it with console.log, and then call .map(...) on that variable. Once you're done checking, you can turn it back into a chain.

Everyone gets 5 bonus points for attending a make-up class. Compare each adjusted score with the passing mark of 75. records is already declared.

① Using a chain, create an array passedScores that adds 5 points to everyone's score and then keeps only scores of 75 or higher.

② Display passedScores joined with ", ".

③ Display the score of the second item in records.

④ Using a chain, get the names of everyone who reaches 75 or higher after the bonus, and display them joined with " / ".

JavaScript / TypeScript Editor

Run code to see output

Getting the Position — The Callback's index

Say you want to add ranks like "#1" and "#2" to a ranking list. The elements themselves don't store a rank, so it's tempting to keep a counter variable outside the callback and add 1 to it on every call.

map and filter pass the element as the callback's first argument, and that element's position (its index, counted from 0) as the second. Arguments are matched by position, so even if you only need the index, you still have to write the first parameter. Name that second parameter index, and you can use it both to display a rank and to check whether an element is among the first few.

const ranking = ["Wireless Earbuds", "Power Bank", "Phone Stand", "USB Hub"];

// The second argument is the position, counted from 0
const lines = ranking.map((item, index) => `#${index + 1}: ${item}`);
console.log(lines.join("\n"));
// #1: Wireless Earbuds
// #2: Power Bank
// #3: Phone Stand
// #4: USB Hub

// filter's callback receives the same two arguments
const topThree = ranking.filter((item, index) => index < 3);
console.log(topThree.length);   // 3
Two Ways to Use the Same index
map gets(item, index)The 4 items get0 to 3index + 1 goesinto the stringStill 4 items,now rankedfilter gets(item, index)Same 0 to 3as mapKeeps itif index < 3Down to 3,values unchanged
Both callbacks receive the position, counted from 0, as their second argument. map builds the position into the new value, while filter uses it to decide what to keep.

The index passed to filter is the position in the original array. The kept elements shifting forward afterward doesn't affect the check, so index < 3 means the first three items of the original array.

A featured-articles page lists articles in recommended order. articles and viewCounts are already declared, and viewCounts holds each article's view count in the same order.

① Using viewCounts, create an array of strings in the form "Title (N views)" and display them one per line.

② Keep only the articles with 5000 or more views, and display them joined with ", ".

③ Keep only the articles ranked at even numbers, like #2 and #4, and display them joined with ", ".

JavaScript / TypeScript Editor

Run code to see output
QUIZ

Knowledge Check

Answer each question one by one.

Q1After you run prices.map((price) => price * 2), what happens to the original prices?

Q2In courses.filter(isPublished).map(toLabel), which array does map read?

Q3In the callback of ranking.map((item, index) => ...), what is passed to index?