JavaScript map, filter, reduce: Complete Guide With 20+ Examples
[rank_math_toc]
If JavaScript arrays are the backbone of data handling, then map(), filter(), and reduce() are the nervous system. These three methods — along with find(), some(), every(), and sort() — transform how you write JavaScript. They replace clunky loops with declarative, chainable operations that are easier to read, test, and debug. This guide covers JavaScript map, filter, and reduce in depth with real-world examples, performance notes, and the subtle quirks that trip up even experienced developers.
Before diving in, make sure you’re comfortable with JavaScript arrays and arrow functions — we’ll be using both extensively.
JavaScript map() — Transform Every Element
map() creates a new array by applying a function to every element. It never mutates the original. The callback receives three arguments: the current element, its index, and the full array.
const prices = [10, 20, 30, 40];
// Basic: double every price
const doubled = prices.map(price => price * 2);
// [20, 40, 60, 80]
// With index
const labeled = prices.map((price, i) => `Item ${i + 1}: $${price}`);
// ["Item 1: $10", "Item 2: $20", "Item 3: $30", "Item 4: $40"]
// Transform objects
const users = [
{ first: "Alice", last: "Smith", age: 30 },
{ first: "Bob", last: "Jones", age: 25 }
];
const names = users.map(u => `${u.first} ${u.last}`);
// ["Alice Smith", "Bob Jones"]
// Extract specific fields (projection)
const ages = users.map(u => ({ name: u.first, age: u.age }));
// [{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }]
Critical rule: map() always returns an array of the same length as the original. If you want to filter elements out, don’t use map() — use filter().
Common map() Mistakes
// Mistake 1: Forgetting to return (with curly braces)
const broken = [1, 2, 3].map(x => { x * 2 }); // [undefined, undefined, undefined]
const fixed = [1, 2, 3].map(x => { return x * 2 }); // [2, 4, 6]
// Or just drop the braces:
const alsofixed = [1, 2, 3].map(x => x * 2); // [2, 4, 6]
// Mistake 2: Using map() for side effects (use forEach instead)
// DON'T do this — map creates a wasted array
users.map(u => console.log(u.name)); // works but bad practice
users.forEach(u => console.log(u.name)); // correct
// Mistake 3: parseInt with map
["1", "2", "3"].map(parseInt); // [1, NaN, NaN] — WTF?
// parseInt receives (value, index) from map, and index becomes radix!
["1", "2", "3"].map(s => parseInt(s, 10)); // [1, 2, 3] — correct
That parseInt example is a legendary JavaScript interview question. The issue is that map passes the index as the second argument, and parseInt interprets that as the radix (base).
JavaScript filter() — Keep What Matches
filter() creates a new array containing only elements that pass a test function. The callback must return a truthy or falsy value.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Keep only even numbers
const evens = numbers.filter(n => n % 2 === 0);
// [2, 4, 6, 8, 10]
// Filter objects
const products = [
{ name: "Laptop", price: 999, inStock: true },
{ name: "Phone", price: 699, inStock: false },
{ name: "Tablet", price: 499, inStock: true },
{ name: "Watch", price: 299, inStock: true }
];
const affordable = products.filter(p => p.price < 500 && p.inStock);
// [{ name: "Tablet", ... }, { name: "Watch", ... }]
// Remove falsy values from an array
const messy = [0, 1, "", "hello", null, undefined, false, 42, NaN];
const clean = messy.filter(Boolean);
// [1, "hello", 42]
The filter(Boolean) trick is one of the most useful one-liners in JavaScript. Boolean is a function that converts any value to true or false, and filter() keeps only the truthy ones.
JavaScript reduce() — The Swiss Army Knife
reduce() is the most powerful array method — and the most misunderstood. It iterates over an array and accumulates a single result. That result can be anything: a number, string, object, array, or even a Map.
// Syntax: array.reduce((accumulator, currentValue, index, array) => {}, initialValue)
// Sum an array
const total = [10, 20, 30].reduce((sum, num) => sum + num, 0);
// 60
// Step by step:
// sum=0, num=10 → 10
// sum=10, num=20 → 30
// sum=30, num=30 → 60
Always provide an initial value. Without it, reduce() uses the first element as the initial accumulator and starts from the second element. This causes bugs with empty arrays:
// Without initial value — crashes on empty array
[].reduce((sum, n) => sum + n);
// TypeError: Reduce of empty array with no initial value
// With initial value — safe
[].reduce((sum, n) => sum + n, 0); // 0
Real-World reduce() Examples
// 1. Count occurrences
const fruits = ["apple", "banana", "apple", "cherry", "banana", "apple"];
const counts = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
// { apple: 3, banana: 2, cherry: 1 }
// 2. Group by property
const people = [
{ name: "Alice", dept: "Engineering" },
{ name: "Bob", dept: "Marketing" },
{ name: "Charlie", dept: "Engineering" },
{ name: "Diana", dept: "Marketing" }
];
const byDept = people.reduce((groups, person) => {
const dept = person.dept;
groups[dept] = groups[dept] || [];
groups[dept].push(person);
return groups;
}, {});
// { Engineering: [Alice, Charlie], Marketing: [Bob, Diana] }
// 3. Flatten nested arrays (before flat() existed)
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
// [1, 2, 3, 4, 5, 6]
// 4. Pipeline / compose functions
const pipeline = [
str => str.trim(),
str => str.toLowerCase(),
str => str.replace(/\s+/g, "-")
];
const slugify = input => pipeline.reduce((result, fn) => fn(result), input);
slugify(" Hello World "); // "hello-world"
find(), findIndex(), some(), and every()
These methods are for searching and testing — they short-circuit (stop early) when possible, making them efficient for large arrays.
const users = [
{ id: 1, name: "Alice", active: true },
{ id: 2, name: "Bob", active: false },
{ id: 3, name: "Charlie", active: true }
];
// find() — First element matching condition (or undefined)
const bob = users.find(u => u.name === "Bob");
// { id: 2, name: "Bob", active: false }
// findIndex() — Index of first match (or -1)
const bobIdx = users.findIndex(u => u.name === "Bob"); // 1
// some() — Does ANY element match? (returns boolean)
const hasInactive = users.some(u => !u.active); // true
// every() — Do ALL elements match? (returns boolean)
const allActive = users.every(u => u.active); // false
some() and every() are short-circuit evaluated. some() stops at the first true, and every() stops at the first false. For large datasets, this is significantly faster than filter().length > 0.
forEach() vs for...of — Which Should You Use?
This debate is surprisingly nuanced. Both iterate over arrays, but they're not interchangeable.
const items = ["a", "b", "c"];
// forEach — callback-based, cannot break/continue
items.forEach((item, index) => {
console.log(`${index}: ${item}`);
// return; // This does NOT break the loop! It's like continue.
});
// for...of — statement-based, supports break/continue/return
for (const item of items) {
if (item === "b") break; // works!
console.log(item);
}
// Output: "a"
// for...of with index (using entries)
for (const [index, item] of items.entries()) {
console.log(`${index}: ${item}`);
}
Use for...of when you need to break, use await in each iteration, or want cleaner error handling with try/catch. Use forEach() when you want a concise callback and don't need to break.
flat() and flatMap() — Flatten Nested Arrays
// flat() — Flatten by specified depth (default: 1)
const nested = [1, [2, 3], [4, [5, 6]]];
nested.flat(); // [1, 2, 3, 4, [5, 6]]
nested.flat(2); // [1, 2, 3, 4, 5, 6]
nested.flat(Infinity); // [1, 2, 3, 4, 5, 6] — flatten everything
// flatMap() — map() + flat(1) in one step
const sentences = ["Hello world", "Goodbye moon"];
const words = sentences.flatMap(s => s.split(" "));
// ["Hello", "world", "Goodbye", "moon"]
// Without flatMap:
const wordsVerbose = sentences.map(s => s.split(" ")).flat();
// Same result, but two iterations instead of one
// Practical: expand items
const cart = [
{ item: "Shirt", colors: ["red", "blue"] },
{ item: "Pants", colors: ["black"] }
];
const expanded = cart.flatMap(product =>
product.colors.map(color => `${color} ${product.item}`)
);
// ["red Shirt", "blue Shirt", "black Pants"]
sort() and Its Dangerous Default Behavior
JavaScript's sort() has one of the most surprising default behaviors in the language. Without a comparison function, it converts everything to strings and sorts lexicographically:
// The infamous sort bug
[10, 9, 80, 1, 21].sort();
// [1, 10, 21, 80, 9] — sorted as strings!
// Fix: provide a comparison function
[10, 9, 80, 1, 21].sort((a, b) => a - b);
// [1, 9, 10, 21, 80] — correct numeric sort
// Descending
[10, 9, 80, 1, 21].sort((a, b) => b - a);
// [80, 21, 10, 9, 1]
// Sort objects by property
const users = [
{ name: "Charlie", age: 35 },
{ name: "Alice", age: 28 },
{ name: "Bob", age: 32 }
];
users.sort((a, b) => a.age - b.age);
// Alice(28), Bob(32), Charlie(35)
// Sort strings properly (locale-aware)
const names = ["Ångström", "Zebra", "apple", "Banana"];
names.sort((a, b) => a.localeCompare(b));
// ["Ångström", "apple", "Banana", "Zebra"]
Important: sort() mutates the original array. Use toSorted() (ES2023) or [...arr].sort() for non-destructive sorting. The comparison function rules are simple: return negative to place a before b, positive for b before a, and zero if they're equal. Read more on MDN's sort() documentation.
Chaining Array Methods
The real power of these methods comes from chaining them together. Each method returns a new array (except reduce(), find(), some(), every()), so you can pipe data through a series of transformations.
const orders = [
{ product: "Laptop", amount: 999, status: "delivered" },
{ product: "Phone", amount: 699, status: "pending" },
{ product: "Tablet", amount: 499, status: "delivered" },
{ product: "Watch", amount: 299, status: "cancelled" },
{ product: "Headphones", amount: 149, status: "delivered" }
];
// Get total revenue from delivered orders
const deliveredRevenue = orders
.filter(o => o.status === "delivered")
.map(o => o.amount)
.reduce((sum, amount) => sum + amount, 0);
// 1647
// Get sorted list of delivered product names
const deliveredProducts = orders
.filter(o => o.status === "delivered")
.map(o => o.product)
.sort();
// ["Headphones", "Laptop", "Tablet"]
Performance Considerations for Chaining
Each chained method creates a new intermediate array. For small arrays (under 10,000 elements), this is negligible. For large datasets, consider using a single reduce() instead:
// Chained (3 array iterations):
const result = bigArray
.filter(x => x > 10)
.map(x => x * 2)
.reduce((sum, x) => sum + x, 0);
// Single reduce (1 iteration):
const result2 = bigArray.reduce((sum, x) => {
if (x > 10) sum += x * 2;
return sum;
}, 0);
// Same result, but 3x fewer iterations
In most applications, the chained version is preferred for readability. Only optimize to a single reduce() when profiling shows it matters. As the V8 engine team has shown, modern JavaScript engines optimize array methods heavily.
New Array Methods (ES2023+)
Recent JavaScript versions added non-mutating versions of several methods:
const arr = [3, 1, 4, 1, 5];
// toSorted() — non-mutating sort
const sorted = arr.toSorted((a, b) => a - b);
// sorted: [1, 1, 3, 4, 5], arr unchanged
// toReversed() — non-mutating reverse
const reversed = arr.toReversed();
// reversed: [5, 1, 4, 1, 3], arr unchanged
// with() — non-mutating element replacement
const updated = arr.with(2, 99);
// updated: [3, 1, 99, 1, 5], arr unchanged
// toSpliced() — non-mutating splice
const spliced = arr.toSpliced(1, 2, 10, 20);
// spliced: [3, 10, 20, 1, 5], arr unchanged
These immutable methods are a game-changer for React state management and functional programming patterns.
Summary
JavaScript map, filter, and reduce are the foundation of modern JavaScript data processing. map() transforms, filter() selects, and reduce() accumulates. Combined with find(), some(), every(), and the newer immutable methods, you can handle virtually any data transformation without writing a single for loop. Up next, learn how JavaScript objects work — the other half of the data structure equation — and how destructuring lets you extract data from both arrays and objects elegantly.
Further reading: MDN — Array Methods | JavaScript.info — Array Methods | Josh W Comeau — Array Methods