docs/concepts/map-reduce-filter.mdx
How do you transform every item in an array? How do you filter out the ones you don't need? How do you combine them all into a single result? These are the three most common operations you'll perform on arrays, and JavaScript gives you three powerful methods to handle them.
// The power of map, filter, and reduce in action
const products = [
{ name: 'Laptop', price: 1000, inStock: true },
{ name: 'Phone', price: 500, inStock: false },
{ name: 'Tablet', price: 300, inStock: true },
{ name: 'Watch', price: 200, inStock: true }
]
const totalInStock = products
.filter(product => product.inStock) // Keep only in-stock items
.map(product => product.price) // Extract just the prices
.reduce((sum, price) => sum + price, 0) // Sum them up
console.log(totalInStock) // 1500
That's map, filter, and reduce working together. Three methods that transform how you work with arrays.
<Info> **What you'll learn in this guide:** - What map(), filter(), and reduce() do and when to use each - The factory assembly line mental model for array transformations - How to chain methods together for powerful data pipelines - The critical mistake with reduce() that crashes your code - Real-world patterns for extracting, filtering, and aggregating data - Other useful array methods like find(), some(), and every() - How to implement map and filter using reduce (advanced) - How to handle async callbacks with these methods </Info> <Warning> **Prerequisite:** This guide assumes you understand [higher-order functions](/concepts/higher-order-functions). map, filter, and reduce are all higher-order functions that take callbacks. If that concept is new to you, read that guide first! </Warning>Think of these three methods as stations on a factory assembly line. Raw materials (your input array) flow through different stations, each performing a specific job:
┌─────────────────────────────────────────────────────────────────────────┐
│ THE FACTORY ASSEMBLY LINE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Raw Materials PAINTING QUALITY PACKAGING │
│ (Input Array) STATION CONTROL STATION │
│ map() filter() reduce() │
│ │
│ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┐ ┌─────────┐ │
│ │ 1 │ 2 │ 3 │ 4 │ → │ 2 │ 4 │ 6 │ 8 │ → │ 6 │ 8 │ → │ 14 │ │
│ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┘ └─────────┘ │
│ │
│ Transform Keep items Combine into │
│ each item where n > 4 single value │
│ (n × 2) (sum) │
│ │
│ ──────────────────────────────────────────────────────────────────── │
│ │
│ INPUT COUNT SAME COUNT FEWER OR SINGLE │
│ = 4 items = 4 items SAME = 2 OUTPUT │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Painting Station (map): Every item gets transformed. You put in 4 items, you get 4 items out. Each one is changed in the same way.
Quality Control (filter): Items are inspected and only those that pass the test continue. You might get fewer items out than you put in.
Packaging Station (reduce): Everything gets combined into a single package. Many items go in, one result comes out.
The beauty of this assembly line? You can connect the stations in any order. The output of one becomes the input of the next.
These three methods are the workhorses of functional programming in JavaScript. They let you transform, filter, and aggregate data without writing explicit loops and without mutating your original data. According to the State of JS 2023 survey, map, filter, and reduce are among the most commonly used array methods, with the vast majority of JavaScript developers using them regularly in production code.
| Method | What It Does | Returns | Original Array |
|---|---|---|---|
map() | Transforms every element | New array (same length) | Unchanged |
filter() | Keeps elements that pass a test | New array (0 to same length) | Unchanged |
reduce() | Combines all elements into one value | Any type (number, object, array, etc.) | Unchanged |
The map() method creates a new array by calling a function on every element of the original array. Think of it as a transformation machine: every item goes in, every item comes out changed.
The map() method is an array method that creates a new array by applying a callback function to each element of the original array. It returns an array of the same length with each element transformed according to the callback. The original array is never modified, making map a pure, non-mutating operation ideal for functional programming. As MDN documents, map was introduced in ECMAScript 5.1 (2011) and calls the provided function once for each element in order.
const numbers = [1, 2, 3, 4]
const doubled = numbers.map(num => num * 2)
console.log(doubled) // [2, 4, 6, 8]
console.log(numbers) // [1, 2, 3, 4] — original unchanged!
array.map(callback(element, index, array), thisArg)
| Parameter | Description |
|---|---|
element | The current element being processed |
index | The index of the current element (optional) |
array | The array map() was called on (optional) |
thisArg | Value to use as this in callback (optional, rarely used) |
// Double every number
const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map(n => n * 2)
console.log(doubled) // [2, 4, 6, 8, 10]
// Convert to uppercase
const words = ['hello', 'world']
const shouting = words.map(word => word.toUpperCase())
console.log(shouting) // ['HELLO', 'WORLD']
// Square each number
const squares = numbers.map(n => n * n)
console.log(squares) // [1, 4, 9, 16, 25]
One of the most common uses of map is pulling out specific properties from an array of objects:
const users = [
{ id: 1, name: 'Alice', email: '[email protected]' },
{ id: 2, name: 'Bob', email: '[email protected]' },
{ id: 3, name: 'Charlie', email: '[email protected]' }
]
// Get just the names
const names = users.map(user => user.name)
console.log(names) // ['Alice', 'Bob', 'Charlie']
// Get just the emails
const emails = users.map(user => user.email)
console.log(emails) // ['[email protected]', '[email protected]', '[email protected]']
// Get IDs as strings
const ids = users.map(user => `user-${user.id}`)
console.log(ids) // ['user-1', 'user-2', 'user-3']
You can also reshape objects completely:
const users = [
{ firstName: 'Alice', lastName: 'Smith', age: 25 },
{ firstName: 'Bob', lastName: 'Jones', age: 30 }
]
const displayUsers = users.map(user => ({
fullName: `${user.firstName} ${user.lastName}`,
isAdult: user.age >= 18
}))
console.log(displayUsers)
// [
// { fullName: 'Alice Smith', isAdult: true },
// { fullName: 'Bob Jones', isAdult: true }
// ]
Sometimes you need to know the position of each element:
const letters = ['a', 'b', 'c', 'd']
// Add index to each item
const indexed = letters.map((letter, index) => `${index}: ${letter}`)
console.log(indexed) // ['0: a', '1: b', '2: c', '3: d']
// Create objects with IDs
const items = ['apple', 'banana', 'cherry']
const products = items.map((name, index) => ({
id: index + 1,
name
}))
console.log(products)
// [{ id: 1, name: 'apple' }, { id: 2, name: 'banana' }, { id: 3, name: 'cherry' }]
This is a classic JavaScript gotcha. Can you spot the problem?
const strings = ['1', '2', '3']
const numbers = strings.map(parseInt)
console.log(numbers) // [1, NaN, NaN] — Wait, what?!
Why does this happen? Because parseInt takes two arguments: the string to parse and the radix (base). When you pass parseInt directly to map, it receives three arguments: (element, index, array). So the index becomes the radix!
// What's actually happening:
parseInt('1', 0) // 1 (radix 0 defaults to 10)
parseInt('2', 1) // NaN (radix 1 is invalid)
parseInt('3', 2) // NaN (3 is not a valid digit in binary)
The fix: Wrap parseInt in an arrow function or use Number:
// Option 1: Wrap in arrow function
const numbers1 = strings.map(str => parseInt(str, 10))
console.log(numbers1) // [1, 2, 3]
// Option 2: Use Number (simpler)
const numbers2 = strings.map(Number)
console.log(numbers2) // [1, 2, 3]
Both iterate over arrays, but they're for different purposes:
| Aspect | map() | forEach() |
|---|---|---|
| Returns | New array | undefined |
| Purpose | Transform data | Side effects (logging, etc.) |
| Chainable | Yes | No |
| Use when | You need the results | You just want to do something |
const numbers = [1, 2, 3]
// map: When you need a new array
const doubled = numbers.map(n => n * 2)
console.log(doubled) // [2, 4, 6]
// forEach: When you just want to do something with each item
numbers.forEach(n => console.log(n)) // Logs 1, 2, 3
// ❌ WRONG: Using map for side effects (wasteful)
numbers.map(n => console.log(n)) // Creates unused array [undefined, undefined, undefined]
// ✓ CORRECT: Use forEach for side effects
numbers.forEach(n => console.log(n))
The filter() method creates a new array with only the elements that pass a test. Your callback function returns true to keep an element or false to exclude it.
The filter() method is an array method that creates a new array containing only the elements that pass a test implemented by a callback function. Elements where the callback returns true (or a truthy value) are included; elements where it returns false are excluded. Like map, filter never modifies the original array.
const numbers = [1, 2, 3, 4, 5, 6]
const evens = numbers.filter(num => num % 2 === 0)
console.log(evens) // [2, 4, 6]
console.log(numbers) // [1, 2, 3, 4, 5, 6] — original unchanged!
array.filter(callback(element, index, array), thisArg)
The callback receives the same parameters as map(): element, index, array, plus an optional thisArg.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// Keep only even numbers
const evens = numbers.filter(n => n % 2 === 0)
console.log(evens) // [2, 4, 6, 8, 10]
// Keep only odds
const odds = numbers.filter(n => n % 2 !== 0)
console.log(odds) // [1, 3, 5, 7, 9]
// Keep numbers greater than 5
const big = numbers.filter(n => n > 5)
console.log(big) // [6, 7, 8, 9, 10]
// Keep numbers between 3 and 7
const middle = numbers.filter(n => n >= 3 && n <= 7)
console.log(middle) // [3, 4, 5, 6, 7]
const users = [
{ name: 'Alice', age: 25, active: true },
{ name: 'Bob', age: 17, active: true },
{ name: 'Charlie', age: 30, active: false },
{ name: 'Diana', age: 22, active: true }
]
// Keep only active users
const activeUsers = users.filter(user => user.active)
console.log(activeUsers)
// [{ name: 'Alice', ... }, { name: 'Bob', ... }, { name: 'Diana', ... }]
// Keep only adults (18+)
const adults = users.filter(user => user.age >= 18)
console.log(adults)
// [{ name: 'Alice', ... }, { name: 'Charlie', ... }, { name: 'Diana', ... }]
// Keep only active adults
const activeAdults = users.filter(user => user.active && user.age >= 18)
console.log(activeAdults)
// [{ name: 'Alice', ... }, { name: 'Diana', ... }]
The filter callback's return value is evaluated for truthiness. This means you can use filter to remove falsy values:
const mixed = [0, 1, '', 'hello', null, undefined, false, true, NaN, 42]
// Remove all falsy values
const truthy = mixed.filter(Boolean)
console.log(truthy) // [1, 'hello', true, 42]
// This works because Boolean(value) returns true for truthy values
// Boolean(0) → false
// Boolean(1) → true
// Boolean('') → false
// Boolean('hello') → true
// etc.
const products = [
{ name: 'MacBook Pro', category: 'laptops', price: 2000 },
{ name: 'iPhone', category: 'phones', price: 1000 },
{ name: 'iPad', category: 'tablets', price: 800 },
{ name: 'Dell XPS', category: 'laptops', price: 1500 }
]
// Search by name (case-insensitive)
const searchTerm = 'mac'
const results = products.filter(p =>
p.name.toLowerCase().includes(searchTerm.toLowerCase())
)
console.log(results) // [{ name: 'MacBook Pro', ... }]
// Filter by category
const laptops = products.filter(p => p.category === 'laptops')
console.log(laptops) // [{ name: 'MacBook Pro', ... }, { name: 'Dell XPS', ... }]
// Filter by price range
const affordable = products.filter(p => p.price <= 1000)
console.log(affordable) // [{ name: 'iPhone', ... }, { name: 'iPad', ... }]
These methods are related but return different things:
| Method | Returns | Stops Early? | Use Case |
|---|---|---|---|
filter() | Array of all matches | No | Get all matching items |
find() | First match (or undefined) | Yes | Get one item by condition |
some() | true/false | Yes | Check if any match |
every() | true/false | Yes | Check if all match |
const numbers = [1, 2, 3, 4, 5]
// filter: Get ALL even numbers
numbers.filter(n => n % 2 === 0) // [2, 4]
// find: Get the FIRST even number
numbers.find(n => n % 2 === 0) // 2
// some: Is there ANY even number?
numbers.some(n => n % 2 === 0) // true
// every: Are ALL numbers even?
numbers.every(n => n % 2 === 0) // false
const users = [
{ id: 1, name: 'Alice', admin: true },
{ id: 2, name: 'Bob', admin: false },
{ id: 3, name: 'Charlie', admin: false }
]
// ❌ INEFFICIENT: Using filter when you only need one
const result = users.filter(u => u.id === 2)[0] // Checks all elements
// ✓ EFFICIENT: Use find for single item lookup
const user = users.find(u => u.id === 2) // Stops at first match
// ❌ WASTEFUL: Using filter just to check existence
const hasAdmin = users.filter(u => u.admin).length > 0
// ✓ BETTER: Use some for existence check
const hasAdmin2 = users.some(u => u.admin) // true, stops at first admin
The reduce() method executes a "reducer" function on each element, resulting in a single output value. It's the most powerful (and most confusing) of the three.
The reduce() method is an array method that executes a reducer callback function on each element, accumulating the results into a single value. This value can be any type: a number, string, object, or even another array. The callback receives an accumulator (the running total) and the current element, returning the new accumulator value. Always provide an initial value to avoid crashes on empty arrays. The ECMAScript specification notes that calling reduce() on an empty array without an initial value throws a TypeError.
const numbers = [1, 2, 3, 4, 5]
const sum = numbers.reduce((accumulator, current) => accumulator + current, 0)
console.log(sum) // 15
Think of reduce like a snowball rolling down a hill. It starts small (the initial value) and grows as it picks up each element.
array.reduce(callback(accumulator, currentValue, index, array), initialValue)
| Parameter | Description |
|---|---|
accumulator | The accumulated value from previous iterations |
currentValue | The current element being processed |
index | The index of the current element (optional) |
array | The array reduce() was called on (optional) |
initialValue | The starting value for the accumulator (ALWAYS provide this!) |
Let's trace through how reduce works:
const numbers = [1, 2, 3, 4]
const sum = numbers.reduce((acc, curr) => acc + curr, 0)
| Iteration | accumulator | currentValue | acc + curr | New accumulator |
|---|---|---|---|---|
| 1st | 0 (initial) | 1 | 0 + 1 | 1 |
| 2nd | 1 | 2 | 1 + 2 | 3 |
| 3rd | 3 | 3 | 3 + 3 | 6 |
| 4th | 6 | 4 | 6 + 4 | 10 |
┌─────────────────────────────────────────────────────────────────────────┐
│ reduce() STEP BY STEP │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Initial value: 0 │
│ │
│ [1, 2, 3, 4].reduce((acc, curr) => acc + curr, 0) │
│ │
│ Step 1: acc=0, curr=1 → 0 + 1 = 1 (accumulator becomes 1) │
│ Step 2: acc=1, curr=2 → 1 + 2 = 3 (accumulator becomes 3) │
│ Step 3: acc=3, curr=3 → 3 + 3 = 6 (accumulator becomes 6) │
│ Step 4: acc=6, curr=4 → 6 + 4 = 10 (final result!) │
│ │
│ Result: 10 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
const numbers = [10, 20, 30, 40, 50]
// Sum
const sum = numbers.reduce((acc, n) => acc + n, 0)
console.log(sum) // 150
// Average
const average = numbers.reduce((acc, n) => acc + n, 0) / numbers.length
console.log(average) // 30
const numbers = [5, 2, 9, 1, 7]
const max = numbers.reduce((acc, n) => n > acc ? n : acc, numbers[0])
console.log(max) // 9
const min = numbers.reduce((acc, n) => n < acc ? n : acc, numbers[0])
console.log(min) // 1
// Or use Math.max/min with spread (simpler for this case)
console.log(Math.max(...numbers)) // 9
console.log(Math.min(...numbers)) // 1
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1
return acc
}, {})
console.log(count) // { apple: 3, banana: 2, orange: 1 }
const people = [
{ name: 'Alice', department: 'Engineering' },
{ name: 'Bob', department: 'Marketing' },
{ name: 'Charlie', department: 'Engineering' },
{ name: 'Diana', department: 'Marketing' }
]
const byDepartment = people.reduce((acc, person) => {
const dept = person.department
if (!acc[dept]) {
acc[dept] = []
}
acc[dept].push(person)
return acc
}, {})
console.log(byDepartment)
// {
// Engineering: [{ name: 'Alice', ... }, { name: 'Charlie', ... }],
// Marketing: [{ name: 'Bob', ... }, { name: 'Diana', ... }]
// }
const byDepartment = Object.groupBy(people, person => person.department)
This is cleaner for simple grouping, but reduce is still useful when you need custom accumulation logic. Note: Object.groupBy() requires Node.js 21+ or modern browsers (Chrome 117+, Firefox 119+, Safari 17.4+).
</Note>
const pairs = [['name', 'Alice'], ['age', 25], ['city', 'NYC']]
const obj = pairs.reduce((acc, [key, value]) => {
acc[key] = value
return acc
}, {})
console.log(obj) // { name: 'Alice', age: 25, city: 'NYC' }
const nested = [[1, 2], [3, 4], [5, 6]]
const flat = nested.reduce((acc, arr) => acc.concat(arr), [])
console.log(flat) // [1, 2, 3, 4, 5, 6]
// Note: For simple flattening, use .flat() instead
console.log(nested.flat()) // [1, 2, 3, 4, 5, 6]
This shows just how powerful reduce is. You can implement both map and filter using reduce:
// map() implemented with reduce
function myMap(array, callback) {
return array.reduce((acc, element, index) => {
acc.push(callback(element, index, array))
return acc
}, [])
}
const doubled = myMap([1, 2, 3], n => n * 2)
console.log(doubled) // [2, 4, 6]
// filter() implemented with reduce
function myFilter(array, callback) {
return array.reduce((acc, element, index) => {
if (callback(element, index, array)) {
acc.push(element)
}
return acc
}, [])
}
const evens = myFilter([1, 2, 3, 4, 5], n => n % 2 === 0)
console.log(evens) // [2, 4]
The real magic happens when you chain these methods together. Each method returns a new array (or value), which you can immediately call another method on.
const transactions = [
{ type: 'sale', amount: 100 },
{ type: 'refund', amount: 30 },
{ type: 'sale', amount: 200 },
{ type: 'sale', amount: 150 },
{ type: 'refund', amount: 50 }
]
const totalSales = transactions
.filter(t => t.type === 'sale') // Keep only sales
.map(t => t.amount) // Extract amounts
.reduce((sum, amount) => sum + amount, 0) // Sum them up
console.log(totalSales) // 450
When you see a chain, read it like a data pipeline. Data flows from top to bottom, transformed at each step:
const result = data
.filter(...) // Step 1: Remove unwanted items
.map(...) // Step 2: Transform remaining items
.filter(...) // Step 3: Filter again if needed
.reduce(...) // Step 4: Combine into final result
const cart = [
{ name: 'Laptop', price: 1000, quantity: 1, discountPercent: 10 },
{ name: 'Mouse', price: 50, quantity: 2, discountPercent: 0 },
{ name: 'Keyboard', price: 100, quantity: 1, discountPercent: 20 }
]
const total = cart
.map(item => {
const subtotal = item.price * item.quantity
const discount = subtotal * (item.discountPercent / 100)
return subtotal - discount
})
.reduce((sum, price) => sum + price, 0)
console.log(total) // 900 + 100 + 80 = 1080
const users = [
{ email: '[email protected]', active: true, plan: 'premium' },
{ email: '[email protected]', active: false, plan: 'premium' },
{ email: '[email protected]', active: true, plan: 'free' },
{ email: '[email protected]', active: true, plan: 'premium' }
]
const premiumEmails = users
.filter(u => u.active)
.filter(u => u.plan === 'premium')
.map(u => u.email)
console.log(premiumEmails) // ['[email protected]', '[email protected]']
const salespeople = [
{ name: 'Alice', sales: 50000 },
{ name: 'Bob', sales: 75000 },
{ name: 'Charlie', sales: 45000 },
{ name: 'Diana', sales: 90000 },
{ name: 'Eve', sales: 60000 }
]
const top3 = salespeople
.filter(p => p.sales >= 50000) // Minimum threshold
.sort((a, b) => b.sales - a.sales) // Sort descending
.slice(0, 3) // Take top 3
.map(p => p.name) // Get just names
console.log(top3) // ['Diana', 'Bob', 'Eve']
Each method in a chain iterates over the array. For small arrays, this doesn't matter. For large arrays, consider combining operations:
const hugeArray = Array.from({ length: 100000 }, (_, i) => i)
// ❌ SLOW: Three separate iterations
const result1 = hugeArray
.filter(n => n % 2 === 0) // Iteration 1
.map(n => n * 2) // Iteration 2
.filter(n => n > 1000) // Iteration 3
// ✓ FASTER: Single iteration with reduce
const result2 = hugeArray.reduce((acc, n) => {
if (n % 2 === 0) {
const doubled = n * 2
if (doubled > 1000) {
acc.push(doubled)
}
}
return acc
}, [])
This is the most common mistake developers make with reduce, and it can crash your application:
┌─────────────────────────────────────────────────────────────────────────┐
│ THE INITIAL VALUE PROBLEM │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ❌ WITHOUT INITIAL VALUE ✓ WITH INITIAL VALUE │
│ ───────────────────────── ──────────────────── │
│ │
│ [1, 2, 3].reduce((a,b) => a+b) [1, 2, 3].reduce((a,b) => a+b, 0) │
│ → Works: 6 → Works: 6 │
│ │
│ [].reduce((a,b) => a+b) [].reduce((a,b) => a+b, 0) │
│ → TypeError! CRASH → Works: 0 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
// ❌ DANGEROUS: No initial value
const numbers = []
const sum = numbers.reduce((acc, n) => acc + n)
// TypeError: Reduce of empty array with no initial value
// ✓ SAFE: Always provide initial value
const safeSum = numbers.reduce((acc, n) => acc + n, 0)
console.log(safeSum) // 0
const products = [
{ name: 'Laptop', price: 1000 },
{ name: 'Phone', price: 500 }
]
// ❌ WRONG: First accumulator will be the first object, not a number!
const total = products.reduce((acc, p) => acc + p.price)
console.log(total) // "[object Object]500" — Oops!
// ✓ CORRECT: Initial value sets the accumulator type
const total2 = products.reduce((acc, p) => acc + p.price, 0)
console.log(total2) // 1500
const numbers = [1, 2, 3]
// ❌ WRONG: No return statement
const doubled = numbers.map(n => {
n * 2 // Missing return!
})
console.log(doubled) // [undefined, undefined, undefined]
// ✓ CORRECT: Explicit return
const doubled2 = numbers.map(n => {
return n * 2
})
console.log(doubled2) // [2, 4, 6]
// ✓ CORRECT: Implicit return (no curly braces)
const doubled3 = numbers.map(n => n * 2)
console.log(doubled3) // [2, 4, 6]
const users = [
{ name: 'Alice', score: 85 },
{ name: 'Bob', score: 92 }
]
// ❌ WRONG: Mutates the original objects
const curved = users.map(user => {
user.score += 5 // Mutates original!
return user
})
console.log(users[0].score) // 90 — Original was changed!
// ✓ CORRECT: Create new objects
const users2 = [
{ name: 'Alice', score: 85 },
{ name: 'Bob', score: 92 }
]
const curved2 = users2.map(user => ({
...user,
score: user.score + 5
}))
console.log(users2[0].score) // 85 — Original unchanged
console.log(curved2[0].score) // 90
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
]
// ❌ INEFFICIENT: Checks entire array, returns array, needs [0]
const user = users.filter(u => u.id === 2)[0]
// ✓ EFFICIENT: Stops at first match, returns the item directly
const user2 = users.find(u => u.id === 2)
const numbers = [1, 2, 3, 4]
// ❌ WRONG: Forgetting to return accumulator
const sum = numbers.reduce((acc, n) => {
acc + n // Missing return!
}, 0)
console.log(sum) // undefined
// ✓ CORRECT: Always return the accumulator
const sum2 = numbers.reduce((acc, n) => {
return acc + n
}, 0)
console.log(sum2) // 10
const users = [
{ name: 'Alice', active: true },
{ name: 'Bob', active: false },
{ name: 'Charlie', active: true }
]
// ❌ HARD TO READ: Everything crammed into reduce
const result = users.reduce((acc, user) => {
if (user.active) {
acc.push(user.name.toUpperCase())
}
return acc
}, [])
// ✓ CLEARER: Use filter + map
const result2 = users
.filter(u => u.active)
.map(u => u.name.toUpperCase())
console.log(result2) // ['ALICE', 'CHARLIE']
JavaScript has many more array methods beyond map, filter, and reduce. Here's a quick reference:
| Method | Returns | Description |
|---|---|---|
find(fn) | Element or undefined | First element that passes test |
findIndex(fn) | Number | Index of first match (-1 if none) |
some(fn) | Boolean | True if any element passes test |
every(fn) | Boolean | True if all elements pass test |
includes(value) | Boolean | True if value is in array |
indexOf(value) | Number | Index of value (-1 if not found) |
flat(depth) | Array | Flattens nested arrays |
flatMap(fn) | Array | map() then flat(1) |
forEach(fn) | undefined | Executes function for side effects |
reduceRight(fn, init) | Any | Like reduce(), but right-to-left |
sort(fn) | Array | Sorts in place (mutates!) |
toSorted(fn) | Array | Returns sorted copy (no mutation) — ES2023 |
reverse() | Array | Reverses in place (mutates!) |
toReversed() | Array | Returns reversed copy (no mutation) — ES2023 |
slice(start, end) | Array | Returns portion (no mutation) |
splice(start, count) | Array | Removes/adds elements (mutates!) |
toSpliced(start, count) | Array | Returns modified copy (no mutation) — ES2023 |
const numbers = [1, 2, 3, 4, 5]
// find: Get first even number
numbers.find(n => n % 2 === 0) // 2
// findIndex: Get index of first even
numbers.findIndex(n => n % 2 === 0) // 1
// some: Is there any number > 4?
numbers.some(n => n > 4) // true
// every: Are all numbers positive?
numbers.every(n => n > 0) // true
// includes: Is 3 in the array?
numbers.includes(3) // true
// flat: Flatten nested arrays
[[1, 2], [3, 4]].flat() // [1, 2, 3, 4]
// flatMap: Map and flatten
[1, 2].flatMap(n => [n, n * 2]) // [1, 2, 2, 4]
// reduceRight: Reduce from right to left
['a', 'b', 'c'].reduceRight((acc, s) => acc + s, '') // 'cba'
// toSorted: Non-mutating sort (ES2023)
const nums = [3, 1, 2]
const sorted = nums.toSorted() // [1, 2, 3]
console.log(nums) // [3, 1, 2] — original unchanged!
// toReversed: Non-mutating reverse (ES2023)
const reversed = nums.toReversed() // [2, 1, 3]
┌─────────────────────────────────────────────────────────────────────────┐
│ CHOOSING THE RIGHT METHOD │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ WHAT DO YOU NEED? USE THIS │
│ ───────────────── ──────── │
│ │
│ Transform every element → map() │
│ Keep some elements → filter() │
│ Combine into single value → reduce() │
│ Find first matching element → find() │
│ Check if any element matches → some() │
│ Check if all elements match → every() │
│ Check if value exists → includes() │
│ Get index of element → findIndex() or indexOf() │
│ Just do something with each → forEach() │
│ Flatten nested arrays → flat() or flatMap() │
│ │
└─────────────────────────────────────────────────────────────────────────┘
One thing that catches developers off guard: map(), filter(), and reduce() don't wait for async callbacks. They run synchronously, which means you'll get an array of Promises instead of resolved values.
const userIds = [1, 2, 3]
// ❌ WRONG: Returns array of Promises, not users
const users = userIds.map(async (id) => {
const response = await fetch(`/api/users/${id}`)
return response.json()
})
console.log(users) // [Promise, Promise, Promise]
Wrap the map result in Promise.all() to wait for all Promises to resolve:
const userIds = [1, 2, 3]
// ✓ CORRECT: Wait for all Promises to resolve
const users = await Promise.all(
userIds.map(async (id) => {
const response = await fetch(`/api/users/${id}`)
return response.json()
})
)
console.log(users) // [{...}, {...}, {...}] — actual user objects
For filter, you need a two-step approach since filter expects a boolean, not a Promise:
const numbers = [1, 2, 3, 4, 5]
// Check if a number is "valid" via async operation
async function isValid(n) {
// Imagine this calls an API
return n % 2 === 0
}
// ❌ WRONG: filter doesn't await
const evens = numbers.filter(async (n) => await isValid(n))
console.log(evens) // [1, 2, 3, 4, 5] — all items! (Promises are truthy)
// ✓ CORRECT: Map to booleans first, then filter
const checks = await Promise.all(numbers.map(n => isValid(n)))
const evens2 = numbers.filter((_, index) => checks[index])
console.log(evens2) // [2, 4]
map() transforms every element — Input array length equals output array length. Use it to change each item in the same way.
filter() keeps matching elements — Returns 0 to all elements. Use it to remove items that don't pass a test.
reduce() combines into one value — Can return any type: number, string, object, array. The "Swiss Army knife" of array methods.
None of these mutate the original array — They always return something new. This makes your code predictable.
Always provide reduce()'s initial value — Empty arrays without an initial value crash. Don't risk it.
Chain methods for powerful pipelines — filter → map → reduce is a common pattern for data processing.
map() must return something — Forgetting the return statement gives you an array of undefined.
Don't use map() for side effects — Use forEach() if you just want to do something with each element.
Use find() for single item lookup — It's more efficient than filter()[0] because it stops at the first match.
Async callbacks need Promise.all() — map/filter/reduce don't wait for async callbacks. Wrap in Promise.all() to resolve them.
</Info>An array of `undefined` values. In JavaScript, functions without an explicit return statement return `undefined`.
```javascript
const numbers = [1, 2, 3]
// Missing return
const result = numbers.map(n => {
n * 2 // No return!
})
console.log(result) // [undefined, undefined, undefined]
```
Always remember to return a value from your map callback, or use implicit return (arrow function without curly braces).
- **filter()** returns an **array** of all matching elements (could be empty)
- **find()** returns the **first matching element** (or undefined if none)
```javascript
const numbers = [1, 2, 3, 4, 5, 6]
numbers.filter(n => n % 2 === 0) // [2, 4, 6] — All matches
numbers.find(n => n % 2 === 0) // 2 — First match only
```
Use `find()` when you only need one result. It's more efficient because it stops searching after the first match.
Without an initial value, reduce uses the first element as the starting accumulator. If the array is empty, there's no first element, so JavaScript throws a TypeError.
```javascript
// No initial value + empty array = crash
[].reduce((acc, n) => acc + n)
// TypeError: Reduce of empty array with no initial value
// With initial value, empty array returns the initial value
[].reduce((acc, n) => acc + n, 0) // 0
```
Always provide an initial value to prevent crashes and make your intent clear.
**Answer:**
The curly braces `{}` create a function body, which requires an explicit `return` statement. Without it, the function returns `undefined`.
```javascript
// ❌ Wrong (returns undefined)
const doubled = numbers.map(n => { n * 2 })
// ✓ Correct (explicit return)
const doubled = numbers.map(n => { return n * 2 })
// ✓ Correct (implicit return, no braces)
const doubled = numbers.map(n => n * 2)
```
**Answer:**
Chain filter → map → reduce:
```javascript
const total = products
.filter(p => p.inStock) // Keep only in-stock
.map(p => p.price) // Extract prices
.reduce((sum, p) => sum + p, 0) // Sum them
console.log(total) // 1300
// Or combine map and reduce:
const total2 = products
.filter(p => p.inStock)
.reduce((sum, p) => sum + p.price, 0)
console.log(total2) // 1300
```
console.log(result)
```
**Answer:**
**18**
Let's trace through:
1. `filter(n => n % 2 === 0)` keeps even numbers: `[2, 4]`
2. `map(n => n * 3)` triples each: `[6, 12]`
3. `reduce((sum, n) => sum + n, 0)` sums them: `0 + 6 + 12 = 18`