Skip to content
beginner

JavaScript Array Methods: map, filter, and reduce

You know how to build an array. Now comes the part where every beginner hits the same wall: you need to change the values, keep only some of them, or total…

Published 2026-09-06Updated 2026-09-1212 min read
Detailed close-up of a green iguana (Iguana iguana) resting on a tree branch, showcasing vibrant colors and textures.
Detailed close-up of a green iguana (Iguana iguana) resting on a tree branch, showcasing vibrant colors and textures. Photo by limoo on Pexels.

You know how to build an array. Now comes the part where every beginner hits the same wall: you need to change the values, keep only some of them, or total them up — and your instinct says "time for a for loop."

Here's the thing. JavaScript gives you three array methods that handle the iteration for you. Once they click, you'll stop writing a dozen lines of loop code for what should take one. The real skill isn't memorizing syntax. It's knowing what each method hands back to you before you even run it.

map changes every item. filter keeps some items. reduce collapses everything into one value. That's the whole mental model. Let's make it stick.

Why You Need These Three Methods

Imagine you have a list of prices and you need to add tax to each one. Your beginner brain reaches for a loop:

const prices = [10, 25, 50];
const withTax = [];

for (let i = 0; i < prices.length; i++) {
  withTax.push(prices[i] * 1.08);
}

console.log(withTax);
[10.8, 27, 54]

That works. But it's a lot of moving parts for a simple idea: "take each price and multiply it." You're managing an index, checking a length, pushing into a new array — none of which has anything to do with the tax math you actually care about.

The array methods you'll learn here let you describe the operation while JavaScript handles the walking. Each one answers a different question:

  • map — "I want a new array where every item has been transformed."
  • filter — "I want a new array with only the items that pass a test."
  • reduce — "I want one value that summarizes the whole array."

Before we go further, you'll want to be comfortable with two things: creating arrays and writing functions. If you've got those down, you're ready. If not, a quick refresher on either will make this tutorial smoother.

map(): Transform Every Item

map() visits every element in your array, runs a function on it, and collects whatever that function returns into a brand-new array.

Here's the smallest useful example. Double every number:

const numbers = [1, 2, 3, 4];
const doubled = numbers.map(function (number) {
  return number * 2;
});

console.log(doubled);
[2, 4, 6, 8]

You can write the same thing more compactly with an arrow function:

const numbers = [1, 2, 3, 4];
const doubled = numbers.map((number) => number * 2);

console.log(doubled);
[2, 4, 6, 8]

Both versions do the same job. The arrow function just skips some typing.

Here's what matters most: map() returns a new array and leaves the original alone.

const numbers = [1, 2, 3];
const doubled = numbers.map((number) => number * 2);

console.log(doubled);
console.log(numbers);
[2, 4, 6]
[1, 2, 3]

The original numbers array is untouched. That's not an accident — it's the whole point. You're transforming data, not destroying it.

The function you pass to map() receives the current element as its first argument. If you need the position of that element, it's available as a second argument:

const fruits = ["apple", "banana", "cherry"];
const labeled = fruits.map((fruit, index) => `${index + 1}. ${fruit}`);

console.log(labeled);
["1. apple", "2. banana", "3. cherry"]

Common mistake: forgetting to return

The most common beginner mistake with map() is forgetting the return statement inside the callback function:

const numbers = [1, 2, 3];
const doubled = numbers.map((number) => {
  number * 2; // no return!
});

console.log(doubled);
[undefined, undefined, undefined]

Every function that doesn't explicitly return a value returns undefined. map() dutifully collects each undefined into the new array. The fix is simple: make sure your callback returns the transformed value.

Knowledge check

Check your understanding

Answer this question before you continue.

What does this code log?
Question 1 of 2Output Prediction

Focus: Predict the new array produced when a map callback returns a transformed value.

const numbers = [2, 4, 6];
const result = numbers.map((number) => number + 1);
console.log(result);
Which change fixes the code so doubled contains the doubled numbers?
Question 2 of 2Debugging

Focus: Identify a missing return statement as the cause of an undefined-filled map result.

const numbers = [1, 2, 3];
const doubled = numbers.map((number) => {
  number * 2;
});

filter(): Keep Only What Matches

filter() runs a true/false test on every element and keeps only the ones where the test returns true. The elements themselves are not changed — they're either kept or dropped.

Say you want only the numbers greater than 10:

const scores = [15, 8, 42, 3, 27];
const highScores = scores.filter((score) => score > 10);

console.log(highScores);
[15, 42, 27]

Notice what happened: 8 and 3 failed the test, so they were left out. The values that passed came through unchanged.

Here's where beginners often get confused. map() and filter() can look similar because both take a function and both return a new array. But they answer different questions:

const numbers = [1, 2, 3, 4];

const doubled = numbers.map((number) => number * 2);
const evens = numbers.filter((number) => number % 2 === 0);

console.log(doubled);
console.log(evens);
[2, 4, 6, 8]
[2, 4]

map() changed every value. filter() kept only the values that passed the test. Same input array, completely different results.

Common mistake: returning a truthy number instead of a test result

Your callback for filter() must return a truthy or falsy value. A common beginner slip is writing something that returns the element itself:

const numbers = [1, 2, 3, 4];
const result = numbers.filter((number) => number % 2 === 0);

That's correct — the comparison number % 2 === 0 evaluates to true or false. But if you accidentally write:

const numbers = [1, 2, 3, 4];
const result = numbers.filter((number) => number % 2);

...you're returning 0 or 1, not a comparison. Since 0 is falsy and 1 is truthy, you might get the right answer by accident — until you don't. Keep your filter callbacks returning clear true/false tests.

Knowledge check

Check your understanding

Answer this question before you continue.

Which method best completes this task: create a new array containing only scores greater than 10, without changing the score values?
Single Choice

Focus: Choose filter() when a task requires keeping only elements that pass a condition.

const scores = [15, 8, 42, 3, 27];
const highScores = scores.____((score) => score > 10);

reduce(): Collapse Everything Into One Value

reduce() is the least intuitive of the three, so let's take it slowly.

Imagine you're walking down a line of numbers with a running total in your hand. You start at 0. You pick up the first number, add it to your total, and carry the new total forward. Then the next number. Then the next. When you reach the end, the total in your hand is the final answer.

That's reduce(). It carries a value forward — called the accumulator — and uses each element to update it.

Here's how you sum an array:

const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((accumulator, currentNumber) => {
  return accumulator + currentNumber;
}, 0);

console.log(sum);
10

Let's trace what happens step by step:

  1. The accumulator starts at 0 (the initial value you passed in).
  2. First element (1): accumulator becomes 0 + 1 = 1.
  3. Second element (2): accumulator becomes 1 + 2 = 3.
  4. Third element (3): accumulator becomes 3 + 3 = 6.
  5. Fourth element (4): accumulator becomes 6 + 4 = 10.

The callback receives the accumulator first, then the current element. Whatever you return from the callback becomes the new accumulator for the next round.

That 0 at the end is the initial value, and it matters. Without it, reduce() uses the first element as the starting accumulator and begins from the second element. For a sum that still works, but for other operations it can produce surprises. Get in the habit of always providing an initial value.

The result doesn't have to be a number

Here's where reduce() gets powerful. The "one value" it produces can be a number, a string, an object, or even an array. It depends entirely on what you build in the accumulator.

const words = ["hello", "world"];
const sentence = words.reduce((accumulator, word) => {
  return accumulator + " " + word;
}, "Start:");

console.log(sentence);
"Start: hello world"

Same mechanism, different result type.

Common mistake: forgetting to return the accumulator

With map() and filter(), forgetting a return gives you an array full of undefined or an empty array. With reduce(), the failure is sneakier because it builds over time.

const numbers = [1, 2, 3];
const sum = numbers.reduce((accumulator, number) => {
  accumulator + number; // no return!
}, 0);

console.log(sum);
NaN

Here's what actually happens:

  1. The accumulator starts at 0.
  2. First element (1): the callback runs 0 + 1 but returns nothing, so the accumulator becomes undefined.
  3. Second element (2): the callback tries undefined + 2, which produces NaN (Not a Number).
  4. Third element (3): NaN + 3 stays NaN.

The final result is NaN, not undefined. If you see NaN from a reduce() call, your first suspect should be a missing return inside the callback. Always return the new accumulator value.

Knowledge check

Check your understanding

Answer this question before you continue.

The code logs `NaN`. What is the direct fix?
Debugging

Focus: Diagnose a missing accumulator return when reduce() produces NaN.

const numbers = [1, 2, 3];
const sum = numbers.reduce((accumulator, number) => {
  accumulator + number;
}, 0);

map, filter, and reduce Side by Side

When you're staring at a problem and wondering which method to reach for, this table is your decision rule:

MethodWhat it doesWhat it returnsUse it when
map()Runs a function on every elementA new array with the same lengthYou want every item changed the same way
filter()Tests every element against a conditionA new array with only the items that passedYou want to keep some items and drop others
reduce()Carries an accumulator through every elementOne single valueYou want a total, summary, or combined result

The quick version: same number of items but changed values means map(). Fewer items means filter(). One summary value means reduce().

These methods also chain together naturally. A common real-world pattern is filtering first, then mapping:

const prices = [10, 25, 50, 5, 100];
const discounted = prices
  .filter((price) => price >= 20)
  .map((price) => price * 0.9);

console.log(discounted);
[22.5, 45, 90]

First we kept only the prices of $20 or more. Then we applied a 10% discount to what remained. Each step passes its result to the next method, and the original array never changes.

Debugging Callback Mistakes by Reading the Output

You'll make these mistakes. Everyone does. The trick is learning to spot them from the output.

Symptom 1: map() returns an array full of undefined.

That means your callback forgot to return a value. Every element became undefined because the callback returned nothing.

Symptom 2: filter() returns an empty array.

Either no element passed your test, or your callback forgot to return a true/false comparison. Check whether you wrote a test that can actually be true for any of your data.

Symptom 3: reduce() returns NaN.

Your accumulator became invalid somewhere in the middle. The usual cause is a missing return in the callback, which turns the accumulator into undefined after the first step.

Symptom 4: The result has the wrong length or wrong values.

If you expected three items and got five, or expected numbers and got something else, check which method you used. The shape of the result tells you which method you actually called.

One more rule while you're learning: do not mutate the input array inside the callback. These methods return new arrays, but your callback can still change the original if you tell it to. Read the input, return the output, and touch nothing else. That keeps your results predictable and your debugging simple.

A debugging habit that saves time: log the result and compare its length and contents to what you expected. The output is evidence. Read it before changing your code.

Practice: Process a Real Array

Flow from scores [72, 88, 55, 91, 64, 79] through map adding 5 to each value, producing [77, 93, 60, 96, 69, 84], then filter keeping scores at least 70, producing [77, 93, 96, 84], then reduce producing total 350 and average 87.5.
Follow the result shape at each step: map creates a same-length array, filter creates a shorter array, and reduce produces one value.

Time to put all three together. Here's a starter array of student scores:

const scores = [72, 88, 55, 91, 64, 79];

Try this three-part task:

  1. Use map() to add 5 bonus points to every score.
  2. Use filter() to keep only scores that are now 70 or above.
  3. Use reduce() to find the average of those passing scores.

Give it a real attempt before peeking at the solution. The goal isn't to get it right on the first try — it's to predict what each step returns before you run it.

Here's one working solution:

const scores = [72, 88, 55, 91, 64, 79];

const withBonus = scores.map((score) => score + 5);
const passing = withBonus.filter((score) => score >= 70);
const total = passing.reduce((accumulator, score) => {
  return accumulator + score;
}, 0);
const average = total / passing.length;

console.log(withBonus);
console.log(passing);
console.log(average);
[77, 93, 60, 96, 69, 84]
[77, 93, 96, 84]
87.5

Notice how each step feeds the next. The map produced new values, the filter narrowed them down, and the reduce collapsed what remained into a single number you could divide.

Now try swapping the order: filter first, then map. Compare the intermediate arrays and the final average. Filtering before mapping means you only transform the scores that matter — a small difference here, but one that grows with larger data.

This same pattern shows up everywhere in browser JavaScript. Think of a product list on a shopping page: you filter to show only items in stock, then map each product object into the label text you display. Transform, select, summarize — once you can predict what each method returns, you'll start seeing opportunities to use them all over your code.

Run the practice example. Inspect the output at each step. Then try the swapped version and watch how the result changes. That curiosity is exactly what will make these methods stick.

Knowledge check

Final check

Finish the article by checking the ideas you just learned.

Which statement correctly matches a method with its usual result?
Question 1 of 2Misconception Check

Focus: Match each array method to the shape of result it is intended to produce.

What is the value of `passing` after this code runs?
Question 2 of 2Output Prediction

Focus: Predict the outputs of chained map(), filter(), and reduce-style processing in a practical array task.

const scores = [72, 88, 55, 91, 64, 79];
const withBonus = scores.map((score) => score + 5);
const passing = withBonus.filter((score) => score >= 70);

References

  1. Array - JavaScript | MDNdeveloper.mozilla.org
  2. Array.prototype.reduce() - JavaScript | MDNdeveloper.mozilla.org
8sources checked
5source domains
5searches run

Research updated Sep 6, 2026

Keep learning

Related tutorials

Continue with nearby JavaScript topics and beginner-friendly explanations.

A library shelf filled with colorful children's books, focused on educational topics.
beginner
9 min read

Arrays and Objects Basics

A variable can hold one value—and that value can be a collection holding many related values. JavaScript arrays and objects are how you build those…

Read tutorial
University student studies alone in a sunlit classroom, Buenos Aires, Argentina.
beginner
9 min read

Basic Operators in JavaScript

You've learned how to store values in variables. Now it's time to make those values do something. JavaScript operators are the verbs of your code—they add,…

Read tutorial