map, filter and reduce look scary until you see them side by side. Each one is just a loop you already know how to write.

Every JavaScript beginner hits the same wall: tutorials casually use map, filter and reduce as if everyone already understands them. They are not magic. Each one is a shortcut for a loop that you would otherwise write by hand. Once you can see the loop hiding underneath, they stop being scary.

By the end you will be able to:

  • Explain what an array is and how to read the arrow-function syntax (p => p + 5).
  • Use map to change every item, filter to keep some items, and reduce to combine everything into one value.
  • Chain them together, and pick the right one without guessing.

You only need to know what a variable is. All code runs in your browser console (press F12) or in Node.js.

First, what is an array?

An array is an ordered list of things kept in one variable. Think of a row of numbered lockers: each locker holds one value, and you find it by its number. In JavaScript you write an array with square brackets and commas.

Each item has an index (its locker number). The first index is 0, not 1.

The methods in this tutorial all work on arrays: they walk through the items one by one and give you something back. Before we meet them, we need one more piece of vocabulary.

The loop these methods are hiding

Say you have three prices and want to add 5 for shipping to each. With a classic for loop you need three things: an empty array to fill, a loop with a counter, and the line that decides what happens to each item.

const prices = [10, 20, 30];
const withShipping = [];          // 1. an empty array to fill

for (let i = 0; i < prices.length; i++) {   // 2. the loop and its counter
  withShipping.push(prices[i] + 5);         // 3. what happens to each item
}
// [15, 25, 35]

Notice how much of that is boilerplate (the same every time): the empty array, the counter, the push. Only the part marked 3 is interesting. map, filter and reduce take care of the boring parts, so you only write the interesting part.

The small function you hand over

Each method asks you for a small function that says what to do with one item. You will almost always see it written as an arrow function:

An arrow function has three parts: a name for the item, an arrow, and the result.

So p => p + 5 means “take an item, call it p, and give back p + 5”. The method runs it once for every item in the array.

map: change every item

map goes through an array and returns a new array of the same length, where every item has been run through your function.

const prices = [10, 20, 30];
const withShipping = prices.map(p => p + 5);
// [15, 25, 35]
map: one item in, one item out. The order and the length never change.

It is exactly the loop from before, minus the boilerplate. The loop version and this one give the same answer.

map works on anything, not just numbers

Very often you will map a list of objects into a list of one property, for example to get just the names:

const users = [
  { name: "Asha", age: 31 },
  { name: "Ben",  age: 17 },
  { name: "Chen", age: 24 },
];

const names = users.map(user => user.name);
// ["Asha", "Ben", "Chen"]

filter: keep only what passes a test

filter also returns a new array. But instead of changing items, it asks a yes/no question about each one and keeps only the items that answer “yes”. The function you give it must return true or false.

const ages = [12, 19, 25, 16];
const adults = ages.filter(age => age >= 18);
// [19, 25]
filter: each item is tested. True means keep it, false means drop it. Kept items are not changed.

Here is the loop it replaces, so you can see there is no trick:

const adults = [];
for (const age of ages) {
  if (age >= 18) {
    adults.push(age);
  }
}

The result can be shorter, or even empty, but it can never be longer than the original. If nothing passes, you get an empty array [], not an error.

reduce: combine everything into one value

reduce is the one that trips people up, because it does not return an array. It boils the whole array down to one value: a total, an average, a single object. Think of a shopkeeper adding up a bill, one item at a time, keeping a running total.

const cart = [10, 20, 30];
const total = cart.reduce((sum, price) => sum + price, 0);
// 60

Two things are passed in: a function, and a starting value (the 0 at the end). The function gets two inputs, the running total so far and the next item, and must return the new running total.

reduce: the running total is passed from step to step. The last value is the answer.
Stepsum (so far)price (next item)returns
101010
2102030
3303060

The loop version makes the idea obvious:

let sum = 0;                 // the starting value
for (const price of cart) {
  sum = sum + price;         // update the running total
}

reduce can build objects too

The running value does not have to be a number. Here it is an object that counts how many times each fruit appears:

const fruits = ["apple", "pear", "apple", "fig", "apple", "pear"];

const counts = fruits.reduce((tally, fruit) => {
  tally[fruit] = (tally[fruit] || 0) + 1;   // add 1 to this fruit's count
  return tally;                             // hand the tally to the next round
}, {});
// { apple: 3, pear: 2, fig: 1 }

Putting them together

Because map and filter return arrays, you can attach the next method straight after. Real code often chains two or three of them. Suppose you want the total money from paid orders only:

const orders = [
  { id: 1, paid: true,  amount: 40 },
  { id: 2, paid: false, amount: 15 },
  { id: 3, paid: true,  amount: 25 },
  { id: 4, paid: true,  amount: 10 },
];

const revenue = orders
  .filter(order => order.paid)         // keep paid orders
  .map(order => order.amount)          // turn each order into its amount
  .reduce((sum, amount) => sum + amount, 0);   // add them up
// 75
A chain works like an assembly line: each step receives the result of the step before.

Read a chain from top to bottom in plain English: “take the orders, keep the paid ones, turn each into its amount, add them up.” It is also common to filter first and then map, like getting the names of adult users:

const names = users
  .filter(user => user.age >= 18)
  .map(user => user.name);
// ["Asha", "Chen"]

Which one do you actually need?

Ask one question: what shape do I want back?

Choose by the shape of the answer, not by the shape of the input.
MethodReturnsLength of resultTypical use
mapa new arraysame as the originalformat data, pick one property
filtera new arraysame or shortersearch, remove unwanted items
reduceany single valuen/atotals, counts, grouping

Common mistakes

What happensWhyFix
map gives [undefined, undefined, undefined]You wrote curly braces { } but forgot returnWrite n => n * 2, or n => { return n * 2; }
filter keeps everything (or nothing)The function does not return true or false, for example it is missing returnReturn a comparison such as age >= 18
reduce crashes on an empty arrayNo starting value was givenPass a starting value as the second argument
Total looks like 32.400000000000006Decimal numbers are stored approximately in JavaScriptWork in whole cents, or round at the end with toFixed(2)
["1","2","3"].map(parseInt) gives [1, NaN, NaN]map also passes the index, and parseInt reads it as a second argumentWrite .map(s => parseInt(s, 10)) or .map(Number)
You use map only to run something (like console.log)map is for building a new arrayUse forEach when you do not need a result

Try it yourself

Open your browser console and try these. Click each answer only after you have had a go.

1. Turn [1, 2, 3, 4] into [1, 4, 9, 16].

Show answer
[1, 2, 3, 4].map(n => n * n);

2. From ["apple", "fig", "banana", "kiwi"], keep only words longer than 4 letters.

Show answer
["apple", "fig", "banana", "kiwi"].filter(w => w.length > 4);
// ["apple", "banana"]

3. Find the total length of all the words in the same list.

Show answer
["apple", "fig", "banana", "kiwi"]
  .map(w => w.length)
  .reduce((sum, n) => sum + n, 0);
// 18