map, filter, reduce
The Array Transformation Trio
LinkedIn Hook
Give me three array methods and I can build half of a functional codebase.
maptransforms every element.filterkeeps only the ones that match.reducecollapses 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.
mapinsists on same-length output.filteralways walks the whole array.reduceon an empty array with no initial value throws aTypeErrorthat crashes production code.In this lesson you'll see the trio in isolation, chained together, used for
groupBy,flatten, andpipe, 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
What You'll Learn
- How
map,filter, andreduceeach transform arrays and why they return new arrays instead of mutating - How to chain them into pipelines and use
reduceto buildgroupBy,flatten, and function composition - How to write
myMap,myFilter, andmyReducepolyfills 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
Common Mistakes
- Using
mapwhen you only want side effects — reach forforEach(or a regular loop); unused returned arrays waste memory. - Calling
reduceon a possibly-empty array with no initial value — it throwsTypeError: 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?
mapreturns a new array with transformed elements.forEachreturnsundefined— it's used purely for side effects. You can chain aftermap, but not afterforEach.
Q: Can reduce replace both map and filter?
Yes.
reduceis 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 suppliedthisArg, push the return value into a fresh result array, and preserve sparse holes by checkingi 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,reduceusesarr[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.