cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

map, filter & reduce

Transform lists declaratively instead of with loops: map every item, filter what stays, reduce many values into one result, chain array methods, flatten nested arrays, and choose forEach only for side effects.

By the end, you can
  • 01
    Choose the right array methodUse map, filter, reduce, flat, flatMap, and forEach for their intended jobs.
  • 02
    Trace callbacks confidentlyPredict callback arguments, return values, accumulator changes, and common gotchas.
  • 03
    Build readable data pipelinesChain small steps and inspect each real intermediate array before reducing to a final value.

Transform lists declaratively

Arrays often hold many similar things: products in a cart, names in a roster, messages in an inbox, points on a chart. You already know how a loop can visit each index. This lesson gives you the array-method vocabulary professionals use when the goal is clearer than the index mechanics.

The short version is a decision chart: use map when every item becomes one new item, filter when some items stay, reduce when many items become one final value, and forEach only when you want a side effect. flat and flatMap help when arrays are tucked inside other arrays.

Built on callbacks

Every method here takes a callback. This lesson assumes Function expressions & callbacks, Arrow functions, Object.keys/values/entries, and Loops. It also prepares you for later Stage 8 lessons titled Higher-order functions and Composition & pipelines.

The array-method job board
MethodUse it whenReturnsOriginal array
mapEvery item becomes one transformed itemA new array with the same lengthNot changed by the method itself
filterItems must pass a test to stayA new array, often shorterNot changed by the method itself
reduceThe whole array becomes one valueWhatever the accumulator becomesNot changed by the method itself
flatNested arrays should be unpackedA new flatter arrayNot changed by the method itself
forEachYou need a side effect, not a resultundefinedOnly changed if your callback changes something

map: transform every item

SAME COUNT

map is the array factory conveyor belt. Every item enters the same machine, your callback runs, and one transformed item comes out. The new array has the same number of slots as the original.

Real-life analogymap is a factory conveyor belt

Imagine notebooks on a belt. A stamping machine prints a price label on every notebook. It does not decide whether notebooks belong in the shipment; it transforms each one in the same way.

In real life: A box enters the machine
In JavaScript: One array item becomes the callback argument
In real life: The machine labels or reshapes it
In JavaScript: The callback returns the transformed value
In real life: One box leaves for every box that entered
In JavaScript: The returned array has the same length

Where the analogy stops: A real factory can reject broken boxes. map does not remove items; use filter when the count may shrink.

These examples prove several map facts at once: braces arrows need an explicit return, returning object literals needs parentheses, map(parseInt) is a classic trap, and the original array is not mutated.

map pitfalls and fixesPop out in the code editor (opens in a new tab)JavaScript
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 prints [undefined, undefined, undefined] because a braces arrow body must say return.
  • Line 3 works because ({ value: n }) means “return this object.” Without parentheses, JavaScript reads braces as a function body.
  • Line 4 prints [1, NaN, NaN]. map passes (value, index, array), and parseInt treats the index as its radix argument.
map does not mutate

The last log still prints [1, 2, 3]. The method creates a new array. Your callback can still mutate objects inside the array, so keep callbacks pure unless mutation is the point.

filter: keep passing items

TRUTHY TEST

filter is a sieve or a bouncer. The callback returns a value for each item. If that value is truthy, the item gets through; if it is falsy, the item is left out. The callback does not need to return the literal boolean true, but comparisons like age >= 18 are nice and clear.

Real-life analogyfilter is a sieve or a bouncer

Filtering is not transformation. The original item that passes the test is copied into the new array. If you want to change the item after keeping it, chain filter and map.

In real life: A sieve lets small grains pass and blocks big pieces
In JavaScript: The callback's truthy/falsy result decides what stays
In real life: A bouncer checks an ID
In JavaScript: A condition like person.age >= 18
In real life: The line outside may shrink
In JavaScript: The returned array may be shorter or empty

Where the analogy stops: A real bouncer can explain the rule. JavaScript only uses the value your callback returns, so write readable tests.

filter keeps truthy callback resultsPop out in the code editor (opens in a new tab)JavaScript
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));

filter(Boolean) is a compact idiom for dropping falsy values such as 0, "", null, undefined, and NaN. Use it when “remove empty values” is really what you mean; do not use it when 0 is a valid value you need to keep.

reduce & reduceRight: fold many values into one

STEP THROUGH

reduce is the most powerful and the easiest to overuse. Its callback receives an accumulator and the current item. Whatever the callback returns becomes the accumulator for the next item. At the end, reduce returns the accumulator.

Real-life analogyreduce is a snowball

The accumulator is the snowball. A sum starts at 0 and gathers each price. A word counter starts as {} and gathers properties. A string builder starts as "" and gathers text.

In real life: A snowball starts small
In JavaScript: The initial accumulator, like 0 or {}
In real life: It rolls over each patch of snow
In JavaScript: Each array item reaches the callback
In real life: It ends as one bigger ball
In JavaScript: The final total, object, string, or array

Where the analogy stops: A snowball always gets bigger. A reducer can also replace, shrink, or reshape the accumulator; the callback return decides.

Watch reduce grow an accumulator
Step 0 of 13Ready
Your turn: follow the blue line

Choose a reduce setup, then step through what the accumulator does.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const total = prices.reduce((acc, price) => {  return acc + price;}, 0);console.log(total);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the reduce setup

Changing the setup starts a fresh recorded run. Predict the first accumulator before stepping.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Prefer an explicit initial value. Omitting it makes the first array item the starting accumulator, so callbacks begin at the second item. On an empty array, [].reduce(callback) throws TypeError: Reduce of empty array with no initial value.

reduceRight uses the same accumulator idea but visits indexes from right to left. That matters when order matters: composing functions right-to-left, rebuilding text from the end, or explaining a right-associative operation.

Chaining methods: build a readable pipeline

INTERACTIVE

Array methods return values, so you can put the next method directly after the previous one. A chain is an assembly line of small machines: filter the cart, map tax onto each item, filter by price, then reduce to one total.

Real-life analogyChaining is an assembly line

A good chain reads like a checklist. If one step gets complicated, split it into a named helper or a named intermediate array before continuing.

In real life: Machine 1 removes broken parts
In JavaScript: .filter(item => item.inStock)
In real life: Machine 2 adds a label
In JavaScript: .map(item => ({ ...item, priceWithTax }))
In real life: Machine 3 boxes the final order
In JavaScript: .reduce((sum, item) => ...)

Where the analogy stops: Real assembly lines hide intermediate states. Good code often names a temporary variable or uses a playground like this one when a chain gets hard to read.

Build a cart pipeline
Shopping cart pipelinePop out in the code editor (opens in a new tab)JavaScript
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);
Real intermediate values5 stages
  1. 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 } ]
  2. filter in-stock[ { "name": "Notebook", "price": 4, "inStock": true }, { "name": "Mug", "price": 8, "inStock": true }, { "name": "Stickers", "price": 3, "inStock": true } ]
  3. map 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 } ]
  4. filter > 5[ { "name": "Mug", "price": 8, "inStock": true, "priceWithTax": 8.8 } ]
  5. reduce total8.8
Try it yourself

The chain ends in one number: 8.8. That is reduce's job.

Toggle each visible method call. The code and every intermediate value update together; no output is invented.
Are chains lazy?

Ordinary arrays are eager: filter and map create real intermediate arrays immediately. That is clear and usually fine. Later, Higher-order functions and Composition & pipelines revisit ways to build reusable or lazy-looking flows.

flat & flatMap: unpack boxes inside boxes

Sometimes the result of a transformation is nested: arrays inside an array. flat returns a new array with nested arrays unpacked by a chosen depth. With no argument it unwraps one level. flatMap maps each item and then flattens one level.

flat and flatMapPop out in the code editor (opens in a new tab)JavaScript
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);

[1, [2, [3]]].flat() produces [1, 2, [3]], because the inner [3] is still one level too deep. Use flat(Infinity) when you intentionally want all levels. flatMap(word => word.split("")) is a compact way to turn words into one stream of letters.

forEach vs map: side effects vs returned arrays

SORTER

The biggest beginner confusion is using forEach when map is needed. forEach calls your callback for each item and returns undefined. It is for side effects: logging, updating the page, pushing into an existing array, or calling a function that changes something outside the callback.

forEach returns undefinedPop out in the code editor (opens in a new tab)JavaScript
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);
map, filter, reduce, or forEach?
  • Turn [2, 4, 6] into [4, 8, 12]
  • Turn user objects into an array of display names
  • Keep only products where inStock is true
  • Keep people with age >= 18
  • Turn cart items into one total price
  • Count how many times each word appears
  • Print every item for debugging
  • Add a class to each existing element on a page
Try it yourself
0 of 8 correct

Sort each task by the method that matches the shape of the result. Same-length array, shorter array, one value, or side effect?

Choose a category for every card. You can change an answer at any time; Reset clears them all.

One more difference: forEach has no normal break. If you need to stop early, use for...of or a search method like some or every.

Where you’ll use this

Array methods show up anywhere data becomes UI. Object.keys/values/entries often pair with map: turn an object into entries, transform the entries, then render or rebuild. Here are reducer patterns beyond sums: counts and groups.

Counting and grouping with reducePop out in the code editor (opens in a new tab)JavaScript
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);

Grouping is common enough that newer JavaScript has Object.groupBy in supporting environments. Feature-check it with typeof Object.groupBy === "function" before relying on it in shared code. reduce remains useful when you need a custom shape or broader compatibility.

Common misconceptions

“map changes the original array.”

map returns a new array. The original array reference is untouched, although objects inside it can still be mutated by a careless callback.

“filter callbacks must return true or false.”

They may return any value. JavaScript keeps the item when the value is truthy. Clear boolean expressions are still easiest to read.

“reduce is only for sums.”

Sums are the first example, not the whole tool. Reduce can build objects, groups, indexes, strings, maximum values, and flattened arrays.

“forEach is just map without a return.”

That missing return is the point. forEach is for side effects and gives you undefined. If you need the transformed values, use map.

“A long chain is always cleaner than a loop.”

Chains are great when each step is tiny and named by the method. If the callbacks get complicated, split the work or use a loop.

Practice: choose and use the method

6 EXERCISES
Exercise 1 · Warm-upDouble numbers with map

Run the code. What array prints?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const numbers = [2, 4, 6];
const doubled = numbers.map(number => number * 2);
console.log(doubled);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeKeep adults with filter

    Which names are left after filtering for adults?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    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(", "));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeTotal a cart with reduce

      Run the reducer and type the total that prints.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      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);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeCount word frequency

        How many times does the word map appear?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        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);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · PracticeFix a missing return

          The starter prints [undefined, undefined, undefined]. Fix it. What prints after the fix?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const numbers = [1, 2, 3];
          const doubled = numbers.map(number => {
            number * 2;
          });
          console.log(doubled);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengePredict the parseInt trap

            Predict this famous output before you run it.

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            console.log(["1", "2", "3"].map(parseInt));

            Answer, then press Check. Spacing and letter case don’t matter.

              Quiz: check your understanding

              8 QUESTIONS

              Read each callback carefully. Your first-try score matters, but every answer explanation is part of the lesson.

              Lesson quiz · 8 questionsScore: first tries count
              1. Question 1 of 8Which method should you reach for when every input item should produce one transformed output item?

                Choose an answer to see the explanation.

              2. Question 2 of 8What does the missing-return map example print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                console.log([1, 2, 3].map(n => { n * 2; }));

                Choose an answer to see the explanation.

              3. Question 3 of 8What does the parseInt map example print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                console.log(["1", "2", "3"].map(parseInt));

                Choose an answer to see the explanation.

              4. Question 4 of 8What does filter(Boolean) print here?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                console.log([0, "hi", "", 42, null].filter(Boolean));

                Choose an answer to see the explanation.

              5. Question 5 of 8What total does this reduce print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                console.log([5, 12, 3].reduce((acc, price) => acc + price, 0));

                Choose an answer to see the explanation.

              6. Question 6 of 8Which statement about reduceRight is true?

                Choose an answer to see the explanation.

              7. Question 7 of 8What does one-level flat print here?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                console.log([1, [2, [3]]].flat());

                Choose an answer to see the explanation.

              8. Question 8 of 8What does forEach return in this code?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const result = [1, 2, 3].forEach(n => n * 2);
                console.log(result);

                Choose an answer to see the explanation.

              Key takeaways

              • map transforms each item and returns a same-length array.
              • filter keeps items whose callback result is truthy.
              • reduce uses an accumulator to fold many items into one final value; give it an initial value.
              • flat unpacks nested arrays; flatMap maps and flattens one level.
              • forEach returns undefined and belongs to side effects, not transformations.

              One-liner.
              Use map to transform, filter to keep, reduce to fold, flat to unpack, and forEach only to do something on the side.

              Up next: Sorting & copying safely.

              CompleteFrontend Clear concepts. Working examples.