JavaScript Interview Prep
Objects & Arrays

map, filter, reduce

The Array Transformation Trio

LinkedIn Hook

Give me three array methods and I can build half of a functional codebase.

map transforms every element. filter keeps only the ones that match. reduce collapses the array into anything — a sum, a string, a new object, a grouped bucket, even a fully composed function pipeline.

Chain them and you get a pure, declarative data pipeline that reads top-to-bottom like English: filter the sales, apply tax, sum the total.

But each one has a gotcha. map insists on same-length output. filter always walks the whole array. reduce on an empty array with no initial value throws a TypeError that crashes production code.

In this lesson you'll see the trio in isolation, chained together, used for groupBy, flatten, and pipe, and finally rebuilt from scratch as polyfills — the exact implementation interviewers love to ask about.

Read the full lesson -> [link]

#JavaScript #FunctionalProgramming #MapFilterReduce #InterviewPrep #Frontend #WebDevelopment #CodingInterview


map, filter, reduce thumbnail


What You'll Learn

  • How map, filter, and reduce each transform arrays and why they return new arrays instead of mutating
  • How to chain them into pipelines and use reduce to build groupBy, flatten, and function composition
  • How to write myMap, myFilter, and myReduce polyfills from scratch — the classic interview challenge

The Holy Trinity

These three methods are the holy trinity of array transformations. They don't mutate the original array and can be chained together for powerful data pipelines.

map — Transform Every Element

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

const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// map always returns a new array of the SAME length
const users = [
  { name: "Rakibul", age: 25 },
  { name: "Karim", age: 30 }
];
const names = users.map(user => user.name);
console.log(names); // ["Rakibul", "Karim"]

filter — Keep What Matches

const numbers = [1, 2, 3, 4, 5, 6, 7, 8];

const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8]

// filter returns a new array of EQUAL or SMALLER length
const adults = users.filter(user => user.age >= 18);

reduce — Transform Array into Anything

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

// Sum
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum); // 15

// Max value
const max = numbers.reduce((max, curr) => curr > max ? curr : max, -Infinity);
console.log(max); // 5

Chaining map, filter, reduce

const transactions = [
  { type: "sale", amount: 100 },
  { type: "refund", amount: -30 },
  { type: "sale", amount: 200 },
  { type: "refund", amount: -50 },
  { type: "sale", amount: 150 }
];

// Get total of sales only, with 10% tax applied
const totalSalesWithTax = transactions
  .filter(t => t.type === "sale")       // keep only sales
  .map(t => t.amount * 1.1)             // apply 10% tax
  .reduce((sum, amount) => sum + amount, 0); // sum up

console.log(totalSalesWithTax); // 495

Common reduce Patterns

groupBy:

const people = [
  { name: "Rakibul", dept: "Engineering" },
  { name: "Karim", dept: "Marketing" },
  { name: "Sara", dept: "Engineering" },
  { name: "Nadia", dept: "Marketing" },
  { name: "Fahim", dept: "Design" }
];

const grouped = people.reduce((groups, person) => {
  const key = person.dept;
  groups[key] = groups[key] || [];
  groups[key].push(person);
  return groups;
}, {});

console.log(grouped);
// {
//   Engineering: [{ name: "Rakibul", ... }, { name: "Sara", ... }],
//   Marketing: [{ name: "Karim", ... }, { name: "Nadia", ... }],
//   Design: [{ name: "Fahim", ... }]
// }

flatten:

const nested = [[1, 2], [3, 4], [5, [6, 7]]];

const flat = nested.reduce((acc, curr) => acc.concat(curr), []);
console.log(flat); // [1, 2, 3, 4, 5, [6, 7]] — one level

// Deep flatten with recursion
function deepFlatten(arr) {
  return arr.reduce(
    (acc, curr) => acc.concat(Array.isArray(curr) ? deepFlatten(curr) : curr),
    []
  );
}
console.log(deepFlatten(nested)); // [1, 2, 3, 4, 5, 6, 7]

pipe (function composition):

const pipe = (...fns) => (input) =>
  fns.reduce((acc, fn) => fn(acc), input);

const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

const transform = pipe(addOne, double, square);
console.log(transform(3)); // 64 — (3+1=4) -> (4*2=8) -> (8*8=64)

Polyfills — Implement from Scratch

Array.prototype.myMap:

Array.prototype.myMap = function(callback, thisArg) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    if (i in this) { // handle sparse arrays
      result.push(callback.call(thisArg, this[i], i, this));
    } else {
      result.length++;
    }
  }
  return result;
};

[1, 2, 3].myMap(x => x * 2); // [2, 4, 6]

Array.prototype.myFilter:

Array.prototype.myFilter = function(callback, thisArg) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    if (i in this && callback.call(thisArg, this[i], i, this)) {
      result.push(this[i]);
    }
  }
  return result;
};

[1, 2, 3, 4].myFilter(x => x % 2 === 0); // [2, 4]

Array.prototype.myReduce:

Array.prototype.myReduce = function(callback, initialValue) {
  let acc = initialValue;
  let startIndex = 0;

  if (acc === undefined) {
    if (this.length === 0) {
      throw new TypeError("Reduce of empty array with no initial value");
    }
    acc = this[0];
    startIndex = 1;
  }

  for (let i = startIndex; i < this.length; i++) {
    if (i in this) {
      acc = callback(acc, this[i], i, this);
    }
  }

  return acc;
};

[1, 2, 3, 4].myReduce((sum, n) => sum + n, 0); // 10

map, filter, reduce visual 1


Common Mistakes

  • Using map when you only want side effects — reach for forEach (or a regular loop); unused returned arrays waste memory.
  • Calling reduce on a possibly-empty array with no initial value — it throws TypeError: Reduce of empty array with no initial value. Always pass an initial value.
  • Mutating the accumulator or array inside a callback — breaks the "pure transformation" contract and makes chained pipelines produce surprising results.

Interview Questions

Q: What is the difference between map and forEach?

map returns a new array with transformed elements. forEach returns undefined — it's used purely for side effects. You can chain after map, but not after forEach.

Q: Can reduce replace both map and filter?

Yes. reduce is the most general — you can implement map, filter, flatten, groupBy, and almost any array transformation using reduce alone. But readability usually favors using the specific method.

Q: What happens if you call reduce on an empty array without an initial value?

It throws a TypeError: Reduce of empty array with no initial value. Always provide an initial value to be safe.

Q: Implement Array.prototype.map from scratch.

Walk the array with an indexed loop, call the callback with (element, index, array) bound to the supplied thisArg, push the return value into a fresh result array, and preserve sparse holes by checking i in this.

Q: Implement reduce to build a groupBy function.

const groupBy = (arr, keyFn) => arr.reduce((g, item) => { const k = keyFn(item); (g[k] ||= []).push(item); return g; }, {}); — the initial value {} is critical, and you append to the bucket per key.

Q: What does reduce throw on an empty array with no initial value?

TypeError: Reduce of empty array with no initial value. If the array is non-empty and no initial value is given, reduce uses arr[0] as the accumulator and starts iteration at index 1.


Quick Reference — Cheat Sheet

MAP / FILTER / REDUCE — QUICK MAP

Signatures:
  arr.map(fn)                 -> new array, same length
  arr.filter(fn)              -> new array, <= length
  arr.reduce(fn, initial)     -> any value

Common pipelines:
  arr.filter(keep).map(transform).reduce(sum, 0)
  arr.reduce((g, x) => { (g[key(x)] ||= []).push(x); return g; }, {})
  pipe(...fns)(input) = fns.reduce((v, fn) => fn(v), input)

Gotchas:
  - reduce with no initial value on [] -> TypeError
  - map is not for side effects -> use forEach / for-of
  - callbacks get (element, index, array)
  - none of these mutate the source array

Previous: Object Static Methods -> keys, values, entries, freeze, seal Next: for...in vs for...of -> Keys vs Values


This is Lesson 7.5 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.

On this page