JavaScriptmap, filter, reduce

map, filter, reduce

map, filter and reduce are the three most-used array methods in modern JavaScript. They all take a callback, none of them mutate the array, and together they cover most of what loops are used for: transform, narrow, summarise. Once they click, your code reads more like a description of what you want than a recipe for how to compute it.

map — one in, one out

map(fn) returns a new array where each element has been replaced by the result of calling fn. Same length, same order, different values.

JS
const nums = [1, 2, 3, 4];

const doubled = nums.map(n => n * 2);
// [2, 4, 6, 8]

const users = [
  { id: 1, name: "Ada"  },
  { id: 2, name: "Lin"  },
];
const names = users.map(u => u.name);
// ["Ada", "Lin"]

// The callback also receives (index, array)
nums.map((n, i) => `#${i}: ${n}`);
// ["#0: 1", "#1: 2", "#2: 3", "#3: 4"]
map is not for side effects
If you only want to run code per element and don't care about the return value, use `forEach` or a plain loop. Using `map` and throwing away the result is a code smell and wastes memory on the discarded array.
filter — keep the ones you want

filter(predicate) returns a new array containing only the elements for which the callback returns truthy. The result is the same shape but typically shorter.

JS
const nums = [1, 2, 3, 4, 5, 6];

const evens = nums.filter(n => n % 2 === 0);
// [2, 4, 6]

const orders = [
  { id: 1, total: 49,  paid: true  },
  { id: 2, total: 0,   paid: false },
  { id: 3, total: 120, paid: true  },
];

orders.filter(o => o.paid && o.total > 0);
// [{ id: 1, ... }, { id: 3, ... }]
Remove falsy values quickly
`arr.filter(Boolean)` drops every `null`, `undefined`, `0`, `""`, `false` and `NaN` in one go. Handy for cleaning up user input or optional fields.
reduce — fold an array into one value

reduce(fn, initial) walks the array carrying an accumulator. The callback receives (acc, element, index, array) and returns the next accumulator. The final accumulator is the return value.

JS
const nums = [1, 2, 3, 4];

// Sum
const sum = nums.reduce((acc, n) => acc + n, 0);   // 10

// Max
const max = nums.reduce((acc, n) => (n > acc ? n : acc), -Infinity);

// Group by key
const people = [
  { name: "Ada",   country: "UK" },
  { name: "Lin",   country: "TW" },
  { name: "Pedro", country: "BR" },
  { name: "Tina",  country: "UK" },
];

const byCountry = people.reduce((acc, p) => {
  (acc[p.country] ??= []).push(p);
  return acc;
}, {});
// { UK: [Ada, Tina], TW: [Lin], BR: [Pedro] }
Always pass an initial value
Calling `reduce` without an initial value uses the first element as the seed and starts at index 1. That fails on an empty array (`TypeError`) and quietly skips your transformation on the first element. Always pass a sensible initial value such as `0`, `""`, `[]` or `{}`.
When to choose which
  • Same length, transformed values? map.

  • Shorter (or same) length, same shape? filter.

  • One summary value (sum, group, max, build object/Map)? reduce.

  • Map then filter or filter then map? Use flatMap to do both in one pass.

  • Side effects only? None of these — use a plain loop.

Chaining

Because each method returns a new array, you can chain them into a small pipeline. Read top-to-bottom: each line is a step.

JS
const orders = [
  { id: 1, total: 49,  status: "paid"    },
  { id: 2, total: 0,   status: "pending" },
  { id: 3, total: 120, status: "paid"    },
  { id: 4, total: 80,  status: "paid"    },
];

const totalPaid = orders
  .filter(o => o.status === "paid")
  .map(o => o.total)
  .reduce((a, b) => a + b, 0);
// 49 + 120 + 80 = 249
Performance notes

Each method in a chain walks the array once and allocates an intermediate array. For small arrays this cost is invisible. For very large arrays (tens of thousands of elements) on hot paths it can matter:

  • A 3-method chain over 100k items does 3 walks and 2 intermediate arrays — usually still fine.

  • If profiling shows it is hot, fuse into a single reduce or for...of loop with push (one walk, one allocation).

  • Premature fusion costs readability — measure first, then optimise.

Single-pass equivalent

JS
const totalPaid = orders.reduce((sum, o) => {
  if (o.status === "paid") sum += o.total;
  return sum;
}, 0);
Tiny gotcha: parseInt with map

JS
["1", "2", "3"].map(parseInt);
// [1, NaN, NaN]   (!)

// Because map calls parseInt(value, index)
// parseInt("2", 1) — radix 1 is invalid → NaN

// Fix: wrap in a one-argument arrow
["1", "2", "3"].map(s => parseInt(s, 10));
// [1, 2, 3]
The lesson
Be deliberate about what the callback receives. Many built-in functions take extra arguments (radix, depth) and `map` passes the index as the second argument — a common source of `NaN` and other surprises.