map, filter & reduce
Transform lists with map, filter, and reduce, then chain methods, flatten nested arrays, and choose forEach for side effects.
- 01Pick the right array methodKnow when to use map, filter, reduce, flat, flatMap, and forEach.
- 02Follow what each function doesSay what inputs your function gets, what it returns, how the running result of reduce changes, and where the common mistakes are.
- 03Chain small stepsWrite one method after another, look at the array after each step, and end with one final value.
Do one job to a whole list
An array is a list of values. Arrays often hold many similar things: products in a cart, names in a class, or messages in an inbox.
You already know how a loop visits each item, one index at a time. With a loop, you write every step yourself: start at 0, read the item, move to the next index. Arrays also have built-in methods that do the walking for you. You only say what should happen to each item. Code that says what you want, not how to walk the list, is called declarative code.
Here is which method to pick:
map: every item becomes one new item.filter: some items stay and some are left out.reduce: all the items become one final value, like a total.forEach: you want to do something with each item, like print it, and you don't need a new array back.flatandflatMap: you have arrays inside an array and want one plain list.
You give each of these methods a function. The method calls your function once for each item. A function that you hand to another function, so that it can call it later, is called a callback.
This lesson uses what you learned in Function expressions & callbacks, Arrow functions, Object.keys/values/entries, and Loops. Two later lessons, Higher-order functions and Composition & pipelines, build on it.
The table below puts the five methods side by side. Read each row as: "use this method when…, it gives back…, and the original array…".
| Method | Use it when | What it gives back | Does the original array change? |
|---|---|---|---|
map | Every item becomes one new item. | A new array with the same number of items. | No. The method leaves it as it was. |
filter | Items must pass a test to stay. | A new array, often with fewer items. | No. The method leaves it as it was. |
reduce | The whole array becomes one value. | One value: a number, a string, an object, or anything else. | No. The method leaves it as it was. |
flat | Arrays inside the array should be unpacked. | A new array with fewer arrays inside it. | No. The method leaves it as it was. |
forEach | You want to do something with each item, not get a result. | undefined, which means nothing. | Only if your own function changes it. |
map: change every item
SAME COUNTmap calls your function once for each item. Whatever your function returns goes into a new array. So the new array always has the same number of items as the old one.
Picture notebooks on a moving belt in a factory. A machine stamps a price label on every notebook that passes. It never throws a notebook away. It does the same thing to each one.
- In real life: A notebook rides into the machine
- In JavaScript: One item is handed to your function
- In real life: The machine stamps a price on it
- In JavaScript: Your function returns the new value
- In real life: One notebook comes out for each one that went in
- In JavaScript: The new array has the same number of items
const prices = [3, 5, 8];
const doubled = prices.map(price => price * 2);
console.log(doubled);
console.log(prices);- Line 1 makes an array of three prices.
- Line 2 calls
map. For each price, the arrow function returns that price times 2.mapputs those answers in a new array calleddoubled. - Line 4 prints
[6, 10, 16], the new array. - Line 5 prints
[3, 5, 8]. The originalpricesarray did not change.
map runs your function once for each item. Each answer goes into a new array, in the same place. Three prices go in, so three doubled prices come out. The prices array stays as it was.The next example shows three mistakes people often make with map. The last line checks that the original array is still the same.
const numbers = [1, 2, 3];const missingReturn = numbers.map(n => { n * 2; });const objects = numbers.map(n => ({ value: n }));const parsed = ["1", "2", "3"].map(parseInt); console.log(missingReturn);console.log(objects);console.log(parsed);console.log(numbers);- Line 2 has curly braces around the function body. With braces, an arrow function only gives back a value if you write
return. There is noreturnhere, so each call gives backundefined. Line 6 prints[undefined, undefined, undefined]. - Line 3 wants to return an object, like
{ value: 1 }. The round brackets around the object tell JavaScript "this is a value to return." Without them, JavaScript would read the curly braces as a function body. Line 7 prints three objects. - Line 4 passes
parseIntstraight tomap.parseIntturns text into a whole number. Its second input is the number base: 10 for normal numbers, 2 for binary. The number base is also called the radix. Butmapgives your function three inputs: the item, its index, and the whole array. SoparseIntgets the index as its base. Line 8 prints[1, NaN, NaN].NaNmeans "not a number".
Line 9 still prints [1, 2, 3]. map always builds a new array. But your function can still change an object that sits inside the array. Changing something in place is called mutating it. So inside map, only return new values, and don't change the items.
filter: keep the items that pass a test
TRUTHY TESTfilter calls your function once for each item. Your function is a test. It answers "should this item stay?"
- If the answer is truthy, the item stays. Truthy means "counts as yes":
true, and most other values too. - If the answer is falsy, the item is left out. Falsy means "counts as no". The falsy values you will meet most are
false,0,""(empty text),null,undefined, andNaN.
Your test doesn't have to return exactly true or false. Still, a clear check like age >= 18 is the easiest to read.
A guard stands at a concert door and checks each ticket. People with a valid ticket walk in just as they are. The guard does not change anyone. If you also want to change the items you keep, use filter first and then map.
- In real life: The guard checks each person's ticket
- In JavaScript: Your test runs once for each item
- In real life: A valid ticket means you go in
- In JavaScript: A truthy answer keeps the item
- In real life: The line inside may be shorter, or empty
- In JavaScript: The new array may have fewer items, or none
const cart = [
{ name: "tea", inStock: true },
{ name: "cake", inStock: false },
{ name: "juice", inStock: true },
];
const available = cart.filter(item => item.inStock);
console.log(available.map(item => item.name));
console.log(cart.map(item => item.name));- Lines 1 to 5 make a small cart with three items.
- Line 6 keeps only the items whose
inStockistrue. They go into a new array calledavailable. - Line 8 prints the names that stayed:
["tea", "juice"]. - Line 9 prints
["tea", "cake", "juice"]. The original cart still has all three items.
filter asks your test about each item. Tea and juice pass, so they go into the new array. Cake fails, so it is left out. The items that pass are not changed, and cart still has all three.const values = [0, "hi", "", 42, null, "ok"];const compact = values.filter(Boolean);const adults = [{ age: 17 }, { age: 22 }, { age: 31 }].filter(person => person.age >= 18); console.log(compact);console.log(adults.map(person => person.age));- Line 2 passes
Booleanas the test.Boolean(value)turns any value intotrueorfalse. Sofilter(Boolean)keeps the truthy values and drops the falsy ones. Line 5 prints["hi", 42, "ok"]. - Line 3 keeps the people aged 18 or more. Line 6 prints their ages,
[22, 31].
Use filter(Boolean) when you really mean "remove the empty values". Don't use it when 0 is a real value you need to keep, because 0 is falsy and would be dropped too.
reduce and reduceRight: turn a list into one value
STEP THROUGHreduce turns a whole array into one value, like a total. It does this by carrying a running result from one item to the next. This running result is called the accumulator.
Your function gets two inputs on each turn:
- the accumulator, which is the result so far, and
- the current item.
Whatever your function returns becomes the accumulator for the next item. After the last item, reduce gives back the accumulator. reduce can do a lot, so it is also easy to use it where a simpler method would be clearer.
You roll a small snowball down a hill. Each patch of snow it rolls over sticks to it. At the bottom you have one big ball, made from all the snow it passed.
- In real life: The snowball starts small
- In JavaScript: The start value, like
0or{} - In real life: It rolls over each patch of snow
- In JavaScript: Each item is handed to your function
- In real life: It ends as one big ball
- In JavaScript: The final total, object, text, or array
The accumulator can start as many kinds of value. A sum starts at 0 and adds each price. A word counter starts as an empty object, {}, and adds one property per word. A text builder starts as empty text, "", and adds words to it.
const prices = [2, 3, 5];
const total = prices.reduce((sum, price) => sum + price, 0);
console.log(total);- Line 1 makes an array of three prices.
- Line 2 calls
reduce. The0at the end is the start value forsum, the accumulator. On each turn, the arrow function returnssum + price. - Line 4 prints
10. Three prices became one number.
sum starts at 0. Each turn adds one price and hands the new sum to the next turn. After the last price, reduce gives back the final sum, 10.Step through the code below to watch the accumulator, acc, change on each turn. Try all three ways of starting.
Pick how reduce starts, then step through to watch acc, the running result.
script
const total = prices.reduce((acc, price) => { return acc + price;}, 0);console.log(total);Always give reduce a start value if you can. Here is what happens without one:
reduceuses the first item as the start value. Your function then begins at the second item.- If the array is empty, there is no first item.
[].reduce(callback)then stops with an error:TypeError: Reduce of empty array with no initial value.
reduceRight works just like reduce, but it visits the items from right to left: last item first. Use it when the order matters, for example when you build text starting from the end.
Chaining: one method after another
INTERACTIVEfilter and map give back a new array. So you can call the next method straight on that new array. Writing methods one after another like this is called chaining.
For a shopping cart, a chain could do four steps:
- Keep the items that are in stock.
- Add tax to each price.
- Keep the items that cost more than 5.
- Add the prices into one total.
Parts move from one machine to the next. Each machine does one small job and passes what is left to the next machine.
- In real life: Machine 1 takes out the broken parts
- In JavaScript:
.filter(item => item.inStock) - In real life: Machine 2 sticks a label on each part
- In JavaScript:
.map(item => ({ ...item, priceWithTax })) - In real life: Machine 3 packs everything into one box
- In JavaScript:
.reduce((sum, item) => ...)
A good chain reads like a checklist, one short step per line. If one step gets long, move it into its own named function. Or save the result so far in a variable with a clear name, then go on.
const prices = [2, 5, 8];
const total = prices
.filter(price => price > 3)
.map(price => price * 2)
.reduce((sum, price) => sum + price, 0);
console.log(total);- Line 1 makes the prices 2, 5, and 8.
- Line 3 keeps the prices above 3. That leaves
[5, 8]. - Line 4 doubles them. That gives
[10, 16]. - Line 5 adds them, starting from
0. That gives26. - Line 7 prints
26.
In the playground below, turn each step of the cart chain on or off. You will see the array after every step and the final total.
Line 10 builds a new object for each item with { ...item, priceWithTax: ... }. The three dots copy every property of item into the new object. Then priceWithTax is added next to them.
const cart = [ { name: "Notebook", price: 4, inStock: true }, { name: "Marker", price: 2, inStock: false }, { name: "Mug", price: 8, inStock: true }, { name: "Stickers", price: 3, inStock: true },]; const total = cart .filter(item => item.inStock) .map(item => ({ ...item, priceWithTax: item.price * 1.1 })) .filter(item => item.priceWithTax > 5) .reduce((sum, item) => sum + item.priceWithTax, 0);- start
[ { "name": "Notebook", "price": 4, "inStock": true }, { "name": "Marker", "price": 2, "inStock": false }, { "name": "Mug", "price": 8, "inStock": true }, { "name": "Stickers", "price": 3, "inStock": true } ] - keep in stock
[ { "name": "Notebook", "price": 4, "inStock": true }, { "name": "Mug", "price": 8, "inStock": true }, { "name": "Stickers", "price": 3, "inStock": true } ] - add tax
[ { "name": "Notebook", "price": 4, "inStock": true, "priceWithTax": 4.4 }, { "name": "Mug", "price": 8, "inStock": true, "priceWithTax": 8.8 }, { "name": "Stickers", "price": 3, "inStock": true, "priceWithTax": 3.3 } ] - keep over 5
[ { "name": "Mug", "price": 8, "inStock": true, "priceWithTax": 8.8 } ] - add up total
8.8
The chain ends with one number: 8.8. Making one value is the job of reduce.
Yes. With normal arrays, filter and map build a real new array as soon as they run. A chain of four steps makes the in-between arrays one after another. That is easy to follow and usually fast enough. The later lessons Higher-order functions and Composition & pipelines show other ways to build steps.
flat and flatMap: unpack arrays inside arrays
Sometimes an array holds other arrays. An array inside an array is called a nested array. Each array inside is one level deeper.
flatgives back a new array with the inner arrays unpacked. With no input, it unpacks one level.flatMapdoes amapfirst, then unpacks one level.
const boxes = [["tea", "cake"], ["juice"]];
const items = boxes.flat();
console.log(items);- Line 1 puts two small arrays inside
boxes. - Line 2 calls
flat. It takes the items out of the two small arrays and puts them in one new list. - Line 4 prints
["tea", "cake", "juice"].boxesitself did not change.
const nested = [1, [2, [3]]];const oneLevel = nested.flat();const allLevels = nested.flat(Infinity);const letters = ["map", "fun"].flatMap(word => word.split("")); console.log(oneLevel);console.log(allLevels);console.log(letters);- Line 1 has an array three levels deep:
[3]sits inside[2, [3]], which sits inside the outer array. - Line 2 unpacks one level. Line 6 prints
[1, 2, [3]]. The[3]was one level too deep, so it is still an array. - Line 3 passes
Infinity, which means "keep unpacking until no arrays are left". Line 7 prints[1, 2, 3]. - Line 4 splits each word into its letters with
word.split(""). That makes one array of letters per word.flatMapthen unpacks them into one list. Line 8 prints the six letters.
forEach or map: doing something vs. making a new array
SORTERBeginners often use forEach when they need map. Here is the difference:
forEachcalls your function for each item. Then it gives backundefined, which means nothing.mapcalls your function for each item. Then it gives back a new array of your function's answers.
So forEach is only for side effects. A side effect is a change that your function makes outside itself, such as:
- printing to the console,
- changing the page,
- pushing into an array that already exists.
const names = ["Asha", "Bo"];
const greetings = [];
names.forEach(name => greetings.push("Hi " + name));
console.log(greetings);- Line 1 makes two names. Line 2 makes an empty array,
greetings. - Line 3 runs once per name. Each time, it pushes a greeting into
greetings. That push is the side effect. - Line 5 prints
["Hi Asha", "Hi Bo"]. The useful result is ingreetings, not in anythingforEachgave back.
const numbers = [1, 2, 3];const mapped = numbers.map(n => n * 2);const visited = [];const eachResult = numbers.forEach(n => visited.push(n * 2)); console.log(mapped);console.log(visited);console.log(eachResult);- Line 2 uses
map. Line 6 prints the new array,[2, 4, 6]. - Line 4 uses
forEachto push the same doubled numbers intovisited. Line 7 prints[2, 4, 6]. - Line 8 prints
undefined. That is whatforEachgave back on line 4.
map collects the answers into a new array. forEach throws the answers away and gives back undefined, so it is only useful for doing something, like printing.- Turn
[2, 4, 6]into[4, 8, 12] - Turn a list of user objects into a list of their names
- Keep only the products where
inStockis true - Keep the people with
age >= 18 - Turn the cart items into one total price
- Count how many times each word appears
- Print every item to check your code
- Add a class to each element that is already on the page
Put each task under the method that fits. Ask: do I want an array of the same length, a shorter array, one value, or just to do something?
One more difference: you can't stop forEach early. break does not work inside it. If you need to stop early, use a for...of loop. Or use some or every, which stop as soon as they know the yes-or-no answer.
Where you'll use this
You will use these methods whenever you turn data into something on the page. For example, you can turn an object into a list of pairs with Object.entries, change each pair with map, and then show the result.
reduce can do more than add numbers. The code below uses it twice: once to count words, and once to put words into groups.
const words = ["tea", "coffee", "tea", "juice"];const counts = words.reduce((result, word) => { result[word] = (result[word] ?? 0) + 1; return result;}, {}); const grouped = words.reduce((result, word) => { const first = word[0]; (result[first] ??= []).push(word); return result;}, {}); console.log(counts);console.log(grouped);- Lines 2 to 5 count each word. The accumulator,
result, starts as an empty object,{}. - Line 3 reads the old count for the word. If there is none yet,
?? 0uses 0 instead. Then it adds 1 and stores the new count. - Line 4 returns the same object, so the next turn gets it.
- Lines 7 to 11 group words by their first letter. Line 8 reads the first letter, like
"t"for"tea". - Line 9 uses
??=. It creates an empty array for that letter only if there isn't one yet. Then it pushes the word in. - Line 13 prints
{ tea: 2, coffee: 1, juice: 1 }. Line 14 prints the groups.
Newer JavaScript has a built-in method for grouping, Object.groupBy. Some older browsers don't have it. So check first that it exists: typeof Object.groupBy === "function". If it doesn't exist, use reduce. reduce also works when you need a result shaped in your own way.
map,filter, andforEachgive your function three inputs: the item, its index, and the whole array. Some functions, likeparseInt, read their second input in their own way. Wrap them in your own arrow function, liketext => parseInt(text, 10).- These methods don't wait. If your function is
async,mapgives back an array of promises. A promise is a value that arrives later. To wait for all of them, usePromise.all(items.map(loadItem)). - An array can have empty slots, where no value was ever put.
mapandfilterskip those slots. If every position matters, fill the empty slots first, or use a loop. - Each step in a chain runs right away and builds its own new array. For a very long list, or when you need to stop early, a plain loop can do less work and be easier to check.
- To find one item or get a yes-or-no answer, use
find,some, orevery. Don't force that question intoreduce. - Keep the accumulator the same kind of value from start to end. A number total starts at
0. A grouped result starts at{}.
Common misconceptions
"map changes the original array."
map gives back a new array. The original array stays as it was. But a careless function can still change the objects that sit inside the array.
"A filter test must return true or false."
It can return any value. JavaScript keeps the item when the value is truthy, which means it counts as yes. A clear check like age >= 18 is still the easiest to read.
"reduce is only for sums."
A sum is just the first example. reduce can also build objects, groups, text, a largest value, or a flat array.
"forEach is just map without a return."
The missing return is the whole difference. forEach gives back undefined, so it is only for side effects. If you need the new values, use map.
"A long chain is always better than a loop."
A chain is easy to read when each step is short. If the functions inside get long, split the work into named steps, or use a loop.
Practice: pick the method and use it
6 EXERCISESRun the code. What array does it print?
const numbers = [2, 4, 6];
const doubled = numbers.map(number => number * 2);
console.log(doubled);const numbers = [2, 4, 6];
const doubled = numbers.map(number => number * 2);
console.log(doubled);Each number gives back its double, so map builds the new array [4, 8, 12].
Which names are left after the filter keeps only the adults?
const people = [
{ name: "Ari", age: 17 },
{ name: "Bea", age: 22 },
{ name: "Cal", age: 31 },
];
const adults = people.filter(person => person.age >= 18);
console.log(adults.map(person => person.name).join(", "));const people = [
{ name: "Ari", age: 17 },
{ name: "Bea", age: 22 },
{ name: "Cal", age: 31 },
];
const adults = people.filter(person => person.age >= 18);
console.log(adults.map(person => person.name).join(", "));Ari is 17, so Ari fails the test and is left out. Bea and Cal pass, so they stay.
Run the code. Type the total that it prints.
const cart = [
{ name: "Pencil", price: 1.5 },
{ name: "Notebook", price: 4 },
{ name: "Mug", price: 8 },
];
const total = cart.reduce((sum, item) => sum + item.price, 0);
console.log(total);const cart = [
{ name: "Pencil", price: 1.5 },
{ name: "Notebook", price: 4 },
{ name: "Mug", price: 8 },
];
const total = cart.reduce((sum, item) => sum + item.price, 0);
console.log(total);sum starts at 0. Then it becomes 1.5, then 5.5, and finally 13.5.
How many times does the word map appear?
const words = ["map", "filter", "map", "reduce", "map"];
const counts = words.reduce((result, word) => {
result[word] = (result[word] ?? 0) + 1;
return result;
}, {});
console.log(counts.map);const words = ["map", "filter", "map", "reduce", "map"];
const counts = words.reduce((result, word) => {
result[word] = (result[word] ?? 0) + 1;
return result;
}, {});
console.log(counts.map);The word map is in the list three times, so counts.map is 3.
The starter code prints [undefined, undefined, undefined]. Fix it. What does it print after the fix?
const numbers = [1, 2, 3];
const doubled = numbers.map(number => {
number * 2;
});
console.log(doubled);const numbers = [1, 2, 3];
const doubled = numbers.map(number => {
return number * 2;
});
console.log(doubled);With return, each call gives back its doubled number, and map puts it in the new array.
This output surprises many people. Predict it before you run it.
console.log(["1", "2", "3"].map(parseInt));console.log(["1", "2", "3"].map(parseInt));The calls are parseInt("1", 0), parseInt("2", 1), and parseInt("3", 2). Base 0 means "pick for me", so the first gives 1. Base 1 does not exist, and base 2 has no digit 3. So the result is [1, NaN, NaN].
Quiz: check your understanding
8 QUESTIONSRead each function carefully and work out what it returns. After you answer, read the explanation for every choice, even the wrong ones.
Question 1 of 8Every item in your array should become one new item. Which method do you use?
Choose an answer to see the explanation.
Question 2 of 8This map function has curly braces but no return. What does the code print?
Read the code, then predictconsole.log([1, 2, 3].map(n => { n * 2; }));Choose an answer to see the explanation.
Question 3 of 8What does this map with parseInt print?
Read the code, then predictconsole.log(["1", "2", "3"].map(parseInt));Choose an answer to see the explanation.
Question 4 of 8What does filter(Boolean) print here?
Read the code, then predictconsole.log([0, "hi", "", 42, null].filter(Boolean));Choose an answer to see the explanation.
Question 5 of 8What total does this reduce print?
Read the code, then predictconsole.log([5, 12, 3].reduce((acc, price) => acc + price, 0));Choose an answer to see the explanation.
Question 6 of 8Which sentence about
reduceRightis true?Choose an answer to see the explanation.
Question 7 of 8flat() with no input unpacks one level. What does it print here?
Read the code, then predictconsole.log([1, [2, [3]]].flat());Choose an answer to see the explanation.
Question 8 of 8What does
forEachgive back in this code?Read the code, then predictconst result = [1, 2, 3].forEach(n => n * 2); console.log(result);Choose an answer to see the explanation.
Key takeaways
mapchanges each item and gives back a new array with the same number of items.filterkeeps the items whose test answer is truthy, which means it counts as yes.reducecarries a running result, the accumulator, through the list and gives back one final value. Give it a start value.flatunpacks arrays inside an array.flatMapdoes amap, then unpacks one level.forEachgives backundefined. Use it only to do something, like printing, not to make a new array.
One-liner.
Use map to change every item, filter to keep some items, reduce to make one value, flat to unpack, and forEach only to do something with each item.
Up next: Sorting & copying safely.