Array methods: forEach, map, filter and reduce

Introduction

In JavaScript a function is a value: it can be stored in a variable and passed to another function. A function passed in this way is called a callback. Arrays have methods that call a callback for every item, so most loops over data can be one clear line. The benefit is more than saving typing, because filter tells the reader the purpose at once, while a loop has to be read to find out.

How it works

MethodCalls the callback withGives back
forEach(fn)each itemnothing (undefined)
map(fn)each itema new array of the callback's results
filter(fn)each itema new array of the items where the callback gave true
find(fn)each item until one matchesthat item, or undefined
some(fn), every(fn)each itemtrue if any / all match
reduce(fn, start)a running total and each itemthe final total

Step through the example below: every call of the arrow function gets its own frame on the call stack, with p set to the next item.

reduce. reduce keeps a running value. The callback gets the value so far (often called acc, the accumulator) and the next item, and returns the new value so far:

// reduce: a running total over the items.
const orders = [
  { item: "pen", qty: 3, price: 10 },
  { item: "book", qty: 1, price: 250 },
  { item: "bag", qty: 2, price: 400 },
];
const total = orders.reduce((acc, o) => acc + o.qty * o.price, 0);
console.log("Total:", total);
const names = orders.filter((o) => o.price > 50).map((o) => o.item);
console.log(names);
const sorted = [...orders].sort((a, b) => b.price - a.price);
console.log(sorted.map((o) => o.item).join(", "));
Open in compiler

Sorting. sort() changes the array. Without a callback it sorts as text, so [10, 9, 1].sort() gives [1, 10, 9]. For numbers, pass a compare function: nums.sort((a, b) => a - b). A negative result puts a first, a positive one puts b first. Sorting is stable, so items that compare equal keep their original order. To compare text in a friendly way, use a.localeCompare(b).

Syntax

const doubled = nums.map((n) => n * 2);
const big = nums.filter((n) => n > 10);
const total = nums.reduce((acc, n) => acc + n, 0);
const ordered = [...nums].sort((a, b) => a - b);

Example

// map, filter and find with arrow-function callbacks.
const prices = [120, 45, 300, 80];
const withVat = prices.map((p) => p * 1.15);
console.log(withVat);
const cheap = prices.filter((p) => p < 100);
console.log("Under 100:", cheap);
const big = prices.find((p) => p > 200);
console.log("First over 200:", big);
console.log(prices.some((p) => p > 250), prices.every((p) => p > 50));
Open in compiler

Line by line

  1. prices is the list to work on.
  2. map runs the callback for each price and collects the results. The long decimal is the usual rounding error.
  3. filter keeps only the prices for which the callback gives true.
  4. find stops at the first match, so it gives one item, not a list.
  5. some is true because 300 is over 250. every is false because 45 is not over 50.

Expected result

[ 138, 51.74999999999999, 345, 92 ]
Under 100: [ 45, 80 ]
First over 200: 300
true false

In the real world

A shop page filters products by category, maps them to cards, and reduces the cart to a total. Interface libraries such as React use map to turn data into a list on screen, so you will write these methods constantly.

Common mistakes

  • Forgetting return in a callback with braces. map((x) => { x * 2 }) gives undefined for every item. Write map((x) => x * 2).
  • Using map just to loop. If you do not need the new array, use forEach or for...of.
  • Sorting numbers without a compare function.
  • Forgetting the starting value of reduce. On an empty array it throws a TypeError.
  • Sorting a list you do not own. sort changes it in place, so copy first with [...list].

Best practices

  • Chain small steps, each with one clear job.
  • Keep callbacks short, and give a long one a name.
  • Choose the most specific method: find for one item, some for a yes or no.

Summary

Callbacks let array methods do the looping for you. map transforms, filter selects, find looks for one item and reduce combines everything into one value. sort changes the array, and needs a compare function for numbers.

Quick check

1. What is [1, 2, 3].map((x) => x * 10)?
2. What is [4, 7, 9, 2].filter((x) => x > 5)?
3. What is [3, 4, 5].reduce((acc, x) => acc + x, 0)?
4. What is [10, 9, 1].sort()?

Key takeaways

  • map, filter and reduce give new values and leave the original alone.
  • sort changes the array and compares as text unless you give it a compare function.
  • Chaining small steps often reads better than one large loop.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.