Skip to content
aviral gupta

// B5.2 · ~30 min · Beginner

map, filter and reduce

After this lesson you transform, select and total arrays with map, filter and reduce, chain them, and avoid their classic traps.

Lesson 2 of 5 in B5 Arrays, objects and JSON

You will be able to

  • Write a callback that receives (value, index), build a new array with map and keep items with filter
  • Total an array with reduce and an initial value, and explain the TypeError on an empty array without one
  • Chain the methods, use some and every, and choose between forEach and map
  1. Warm-up · Activity 1 of 7

    Warm-up: what does this print?

    console.log([1, 2, 3].map((n) => n * 10));
  2. Predict · Activity 2 of 7

    Predict before you read on: what does this print?

    const nums = [5, 12, 8, 20];
    console.log(nums.filter((n) => n > 10), nums);
  3. Practice · Activity 3 of 7

    Fill in the initial value so that reduce adds up the numbers and the program prints 9.

    console.log([2, 3, 4].reduce((sum, n) => sum + n, ____));
    console.log([2, 3, 4].reduce((sum, n) => sum + n, ));
  4. Practice · Activity 4 of 7

    Match each call on nums = [1, 2, 3] to what it returns.

  5. Practice · Activity 5 of 7

    The callback takes a second parameter. What does this print?

    console.log(["a", "b", "c"].map((letter, i) => letter + i));
  6. Brain teaser · Activity 6 of 7

    Brain teaser. parseInt("2") is 2. So what does this print?

    console.log(["1", "2", "3"].map(parseInt));
  7. Apply · Activity 7 of 7

    Mini-task: a grade list. Start with five scores out of 100. Use map with the index to make one label per student, such as 1: pass or 2: fail (50 or more passes), and print them joined with ", ". Count the passes with filter, work out the average with reduce and an initial value, and print both. Finally print whether some score is 90 or more, and whether every student passed.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

A week of step counts

Five days of step counts, worked on with the methods of this lesson: map turns them into thousands and into labels, filter keeps the days that reached 10,000 steps, reduce totals the week, some and every answer yes/no questions, and the last part chains filter into reduce. The final line shows that steps itself never changed.

main.js

// Step counts for one working week: transform, select, total.
const steps = [4200, 11800, 7600, 12500, 9100];

const inThousands = steps.map((n) => Math.round(n / 1000));
console.log(inThousands);

const labels = steps.map((n, i) => `Day ${i + 1}: ${n}`);
console.log(labels[0], "|", labels.at(-1));

const goalDays = steps.filter((n) => n >= 10000);
console.log(goalDays, goalDays.length);

const total = steps.reduce((sum, n) => sum + n, 0);
console.log(total, total / steps.length);

console.log(steps.some((n) => n < 5000), steps.every((n) => n > 5000));

const goalTotal = steps.filter((n) => n >= 10000).reduce((sum, n) => sum + n, 0);
console.log(goalTotal, steps);

Run it with

node main.js

Output

[ 4, 12, 8, 13, 9 ]
Day 1: 4200 | Day 5: 9100
[ 11800, 12500 ] 2
45200 9040
true false
24300 [ 4200, 11800, 7600, 12500, 9100 ]
  • map returns as many items as it receives: five counts, five rounded numbers.
  • The labels use the index i, which starts at 0, so the first day is i + 1.
  • reduce starts from 0, the initial value, and adds each count to sum.
  • some is true because one day is under 5000; every is false for the same reason.
  • The chain filters first, then totals only the goal days.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Exercises

Exercise 1 of 2

map or forEach, filter or map

squares(nums) should return a new array with every number squared, and passed(scores) a new array with only the scores of 50 or more. The starter picks the wrong method in both functions: one returns undefined, the other an array of true and false. Fix each with the right method. Keep the word export in front of each function.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Hints
  1. Hint 1

    Run the tests: squares returns undefined. What does forEach always return?

  2. Hint 2

    Use map when you want one new item per old item: nums.map((n) => n * n).

  3. Hint 3

    passed returns [true, false, …] because map keeps whatever the callback returns. Which method keeps the items for which the callback is true?

Show a solution

One way to solve it. Yours can look different and still pass the checks.

// Return a new array with every number squared.
export function squares(nums) {
  return nums.map((n) => n * n);
}

// Return a new array with only the scores of 50 or more.
export function passed(scores) {
  return scores.filter((s) => s >= 50);
}
Run it on your computer

Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

// Return a new array with every number squared.
export function squares(nums) {
  return nums.forEach((n) => n * n);
}

// Return a new array with only the scores of 50 or more.
export function passed(scores) {
  return scores.map((s) => s >= 50);
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {squares, passed} from './main.js';

test('squares([1, 2, 3]) returns [1, 4, 9]', () => {
  const got = squares([1, 2, 3]);
  assert.deepEqual(got, [1, 4, 9], `squares([1, 2, 3]) returned ${JSON.stringify(got)}, expected [1,4,9]`);
});

test('squares leaves the array it was given unchanged', () => {
  const nums = [4, 5];
  squares(nums);
  assert.deepEqual(nums, [4, 5], `after the call the array was ${JSON.stringify(nums)}, expected [4,5]`);
});

test('passed keeps only the scores of 50 or more', () => {
  const got = passed([72, 45, 50, 38]);
  assert.deepEqual(got, [72, 50], `passed([72, 45, 50, 38]) returned ${JSON.stringify(got)}, expected [72,50]`);
});

test('passed returns an empty array when nobody passed', () => {
  const got = passed([10, 20]);
  assert.deepEqual(got, [], `passed([10, 20]) returned ${JSON.stringify(got)}, expected []`);
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Exercise 2 of 2

Total of the big orders

bigOrdersTotal(orders, limit) should add up the orders of limit or more, and return 0 when there are none. anyRefund(orders) should return true when at least one order is negative. The starter totals correctly when some orders are big enough, but stops with a TypeError when none are, and anyRefund asks the wrong question. Fix both.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Hints
  1. Hint 1

    Run the tests: with [5, 3] the filter keeps nothing. What does reduce do with an empty array and no initial value?

  2. Hint 2

    Give reduce an initial value as its second argument: reduce((sum, o) => sum + o, 0).

  3. Hint 3

    every asks "are all orders negative?". Which method asks "is at least one negative?"

Show a solution

One way to solve it. Yours can look different and still pass the checks.

// Add up the orders of limit or more; 0 when there are none.
export function bigOrdersTotal(orders, limit) {
  return orders.filter((o) => o >= limit).reduce((sum, o) => sum + o, 0);
}

// true when at least one order is negative.
export function anyRefund(orders) {
  return orders.some((o) => o < 0);
}
Run it on your computer

Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

// Add up the orders of limit or more; 0 when there are none.
export function bigOrdersTotal(orders, limit) {
  return orders.filter((o) => o >= limit).reduce((sum, o) => sum + o);
}

// true when at least one order is negative.
export function anyRefund(orders) {
  return orders.every((o) => o < 0);
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {bigOrdersTotal, anyRefund} from './main.js';

test('Adds up only the orders of 10 or more', () => {
  const got = bigOrdersTotal([20, 5, 50, 10], 10);
  assert.equal(got, 80, `bigOrdersTotal([20, 5, 50, 10], 10) returned ${got}, expected 80`);
});

test('Returns 0 when no order is big enough', () => {
  const got = bigOrdersTotal([5, 3], 10);
  assert.equal(got, 0, `bigOrdersTotal([5, 3], 10) returned ${got}, expected 0`);
});

test('anyRefund finds one negative order among others', () => {
  const got = anyRefund([12, -4, 30]);
  assert.equal(got, true, `anyRefund([12, -4, 30]) returned ${got}, expected true`);
});

test('anyRefund is false when no order is negative', () => {
  const got = anyRefund([12, 30]);
  assert.equal(got, false, `anyRefund([12, 30]) returned ${got}, expected false`);
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Common mistakes

reduce on an empty array without an initial value

const prices = [];
const total = prices.reduce((sum, p) => sum + p);
console.log(total);

What Node.js prints

TypeError: Reduce of empty array with no initial value

Why, and the fix

Without an initial value, reduce uses the first item as the starting sum, and an empty array has no first item. The same happens after a filter that keeps nothing. Pass a starting value as the second argument, 0 for a sum: prices.reduce((sum, p) => sum + p, 0) returns 0 for an empty array.

Curly braces without return in map

const nums = [1.5, 2.25];
const doubled = nums.map((n) => {
  n * 2;
});
console.log(doubled[0].toFixed(1));

What Node.js prints

TypeError: Cannot read properties of undefined (reading 'toFixed')

Why, and the fix

An arrow function with curly braces returns only what a return statement gives it, as lesson B4.4 showed. Here the callback computes n * 2 and throws it away, so map builds [ undefined, undefined ]. Write return n * 2; inside the braces, or drop the braces: nums.map((n) => n * 2).

Using the result of forEach

const names = ["ada", "linus"];
const upper = names.forEach((n) => n.toUpperCase());
console.log(upper.join(", "));

What Node.js prints

TypeError: Cannot read properties of undefined (reading 'join')

Why, and the fix

forEach runs the callback for each item but always returns undefined, so upper is undefined and has no join method. To get a new array of results, use map: names.map((n) => n.toUpperCase()). Keep forEach for side effects, such as printing each item.

JavaScript in the browser: your browser’s own engine, in a sandboxed worker. Syntax errors are located with acorn 8.18.0, MIT. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

A function for every item

map, filter, reduce, forEach, some and every each take a function, the callback, and call it once per item, as find did in lesson B5.1. The callback receives the item and then its index: (value, index). Arrow functions from B4.4 keep callbacks short: (n) => n * 2. map builds a new array of the same length from what the callback returns: [1, 2, 3].map((n) => n * 10) is [ 10, 20, 30 ]. filter keeps the items for which the callback, a predicate, returns a truthy value, and drops the rest. Neither changes the original array.

reduce folds an array into one value

reduce((sum, n) => sum + n, 0) walks the array with a running result, the accumulator. On the first call sum is the initial value, 0; whatever the callback returns becomes sum for the next item, and the last return is the result. Always pass an initial value: 0 for a sum, 1 for a product, "" for joined text. Without one, reduce starts with the first item, and an empty array has none, so [].reduce((sum, n) => sum + n) stops with TypeError: Reduce of empty array with no initial value. That happens easily after a filter that keeps nothing.

Chains, tests and the parseInt trap

Because map and filter return arrays, you can chain them: steps.filter((n) => n >= 10000).reduce((sum, n) => sum + n, 0). some returns true if the callback is true for at least one item; every returns true only if it is true for all of them. forEach calls the callback for its side effect, such as printing, and returns undefined, so use map when you want a new array. Passing a function by name sends it every argument: ["1", "2", "3"].map(parseInt) gives [ 1, NaN, NaN ], because parseInt reads the index as its second argument, the base. Write map(Number) or map((s) => parseInt(s, 10)).

Sources

Last reviewed October 3, 2026