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
mapto change every item,filterto keep some items, andreduceto 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.
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:
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]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]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);
// 60Two 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.
| Step | sum (so far) | price (next item) | returns |
|---|---|---|---|
| 1 | 0 | 10 | 10 |
| 2 | 10 | 20 | 30 |
| 3 | 30 | 30 | 60 |
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
// 75Read 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?
| Method | Returns | Length of result | Typical use |
|---|---|---|---|
map | a new array | same as the original | format data, pick one property |
filter | a new array | same or shorter | search, remove unwanted items |
reduce | any single value | n/a | totals, counts, grouping |
Common mistakes
| What happens | Why | Fix |
|---|---|---|
map gives [undefined, undefined, undefined] | You wrote curly braces { } but forgot return | Write 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 return | Return a comparison such as age >= 18 |
reduce crashes on an empty array | No starting value was given | Pass a starting value as the second argument |
Total looks like 32.400000000000006 | Decimal numbers are stored approximately in JavaScript | Work 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 argument | Write .map(s => parseInt(s, 10)) or .map(Number) |
You use map only to run something (like console.log) | map is for building a new array | Use 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