Skip to main content
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.
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
Prerequisite: This guide assumes you understand 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!

The Factory Assembly Line

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.

What Are These Methods?

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.
MethodWhat It DoesReturnsOriginal Array
map()Transforms every elementNew array (same length)Unchanged
filter()Keeps elements that pass a testNew array (0 to same length)Unchanged
reduce()Combines all elements into one valueAny type (number, object, array, etc.)Unchanged
The Immutability Principle: None of these methods change the original array. They always return something new. This makes your code predictable and easier to debug.

map() — Transform Every Element

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.

What is map() in JavaScript?

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!

Syntax and Parameters

array.map(callback(element, index, array), thisArg)
ParameterDescription
elementThe current element being processed
indexThe index of the current element (optional)
arrayThe array map() was called on (optional)
thisArgValue to use as this in callback (optional, rarely used)

Basic Transformations

// 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]

Extracting Properties from Objects

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']

Transforming Object Shapes

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 }
// ]

Using the Index Parameter

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' }]

The parseInt Pitfall

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]

map() vs forEach()

Both iterate over arrays, but they’re for different purposes:
Aspectmap()forEach()
ReturnsNew arrayundefined
PurposeTransform dataSide effects (logging, etc.)
ChainableYesNo
Use whenYou need the resultsYou 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))
Don’t use map() when you don’t need the returned array. If you’re just logging or making API calls, use forEach(). Using map for side effects creates an unused array and signals the wrong intent to other developers.

filter() — Keep Matching Elements

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.

What is filter() in JavaScript?

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!

Syntax and Parameters

array.filter(callback(element, index, array), thisArg)
The callback receives the same parameters as map(): element, index, array, plus an optional thisArg.

Basic Filtering

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]

Filtering Objects by Property

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', ... }]

Truthy/Falsy Evaluation

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.
Falsy values in JavaScript: false, 0, -0, 0n (BigInt), "" (empty string), null, undefined, NaN. Everything else is truthy.

Search and Query Filtering

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', ... }]

filter() vs find() vs some() vs every()

These methods are related but return different things:
MethodReturnsStops Early?Use Case
filter()Array of all matchesNoGet all matching items
find()First match (or undefined)YesGet one item by condition
some()true/falseYesCheck if any match
every()true/falseYesCheck 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

reduce() — Combine Into One Value

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.

What is reduce() in JavaScript?

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.

The Anatomy of reduce()

array.reduce(callback(accumulator, currentValue, index, array), initialValue)
ParameterDescription
accumulatorThe accumulated value from previous iterations
currentValueThe current element being processed
indexThe index of the current element (optional)
arrayThe array reduce() was called on (optional)
initialValueThe starting value for the accumulator (ALWAYS provide this!)

Step-by-Step Visualization

Let’s trace through how reduce works:
const numbers = [1, 2, 3, 4]
const sum = numbers.reduce((acc, curr) => acc + curr, 0)
IterationaccumulatorcurrentValueacc + currNew accumulator
1st0 (initial)10 + 11
2nd121 + 23
3rd333 + 36
4th646 + 410
┌─────────────────────────────────────────────────────────────────────────┐
│                         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                                                              │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Common Use Cases

Sum and Average

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

Finding Max/Min

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

Counting Occurrences

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 }

Grouping by Property

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', ... }]
// }
Modern Alternative (ES2024): JavaScript now has Object.groupBy() which does this in one line:
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+).

Building Objects from Arrays

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' }

Flattening Nested Arrays

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]

Implementing map() and filter() with reduce()

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]
When to use reduce: Use reduce when you need to transform an array into a different type (array to object, array to number, etc.) or when you need complex accumulation logic. For simple transformations, map and filter are usually clearer.

Method Chaining — The Real Power

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

Reading Chained Methods

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

Real-World Examples

E-commerce: Calculate discounted total

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

User dashboard: Get active premium users’ emails

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]']

Analytics: Top 3 performers

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']

Performance Considerations

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
}, [])
Performance Rule of Thumb: For arrays under 10,000 items, prioritize readability. For larger arrays or performance-critical code, consider combining operations into a single reduce.

The #1 Mistake: Forgetting reduce()‘s Initial Value

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                            │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Empty Array Without Initial Value = CRASH

// ❌ 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

Type Mismatch Without Initial Value

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
The Rule: Always provide an initial value to reduce(). It prevents crashes on empty arrays and makes your code’s intent clear.

Common Mistakes

map() Mistakes

Forgetting to Return

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]

Mutating Original Objects

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

filter() Mistakes

Using filter When find is Better

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)

reduce() Mistakes

Not Returning the Accumulator

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

Making reduce Too Complex

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']

Other Useful Array Methods

JavaScript has many more array methods beyond map, filter, and reduce. Here’s a quick reference:
MethodReturnsDescription
find(fn)Element or undefinedFirst element that passes test
findIndex(fn)NumberIndex of first match (-1 if none)
some(fn)BooleanTrue if any element passes test
every(fn)BooleanTrue if all elements pass test
includes(value)BooleanTrue if value is in array
indexOf(value)NumberIndex of value (-1 if not found)
flat(depth)ArrayFlattens nested arrays
flatMap(fn)Arraymap() then flat(1)
forEach(fn)undefinedExecutes function for side effects
reduceRight(fn, init)AnyLike reduce(), but right-to-left
sort(fn)ArraySorts in place (mutates!)
toSorted(fn)ArrayReturns sorted copy (no mutation) — ES2023
reverse()ArrayReverses in place (mutates!)
toReversed()ArrayReturns reversed copy (no mutation) — ES2023
slice(start, end)ArrayReturns portion (no mutation)
splice(start, count)ArrayRemoves/adds elements (mutates!)
toSpliced(start, count)ArrayReturns modified copy (no mutation) — ES2023

Quick Examples

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]

Which Method Should I Use?

┌─────────────────────────────────────────────────────────────────────────┐
│                    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()                │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Async Callbacks: The Hidden Gotcha

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]

The Solution: Promise.all()

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

Async Filter is Trickier

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]
For sequential async operations (when order matters or you need to limit concurrency), use a for...of loop instead of map. Array methods run all callbacks immediately in parallel.

Key Takeaways

The key things to remember:
  1. map() transforms every element — Input array length equals output array length. Use it to change each item in the same way.
  2. filter() keeps matching elements — Returns 0 to all elements. Use it to remove items that don’t pass a test.
  3. reduce() combines into one value — Can return any type: number, string, object, array. The “Swiss Army knife” of array methods.
  4. None of these mutate the original array — They always return something new. This makes your code predictable.
  5. Always provide reduce()‘s initial value — Empty arrays without an initial value crash. Don’t risk it.
  6. Chain methods for powerful pipelines — filter → map → reduce is a common pattern for data processing.
  7. map() must return something — Forgetting the return statement gives you an array of undefined.
  8. Don’t use map() for side effects — Use forEach() if you just want to do something with each element.
  9. Use find() for single item lookup — It’s more efficient than filter()[0] because it stops at the first match.
  10. Async callbacks need Promise.all() — map/filter/reduce don’t wait for async callbacks. Wrap in Promise.all() to resolve them.

Test Your Knowledge

Answer:An array of undefined values. In JavaScript, functions without an explicit return statement return undefined.
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).
Answer:
  • filter() returns an array of all matching elements (could be empty)
  • find() returns the first matching element (or undefined if none)
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.
Answer: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.
// 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.
const doubled = numbers.map(n => { n * 2 })
Answer:The curly braces {} create a function body, which requires an explicit return statement. Without it, the function returns undefined.
// ❌ 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)
const products = [
  { name: 'Laptop', price: 1000, inStock: true },
  { name: 'Phone', price: 500, inStock: false },
  { name: 'Tablet', price: 300, inStock: true }
]
Answer:Chain filter → map → reduce:
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
const result = [1, 2, 3, 4, 5]
  .filter(n => n % 2 === 0)
  .map(n => n * 3)
  .reduce((sum, n) => sum + n, 0)

console.log(result)
Answer:18Let’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

Frequently Asked Questions

map() transforms each element and returns a new array. forEach() executes a function on each element but returns undefined. Use map() when you need the transformed result; use forEach() when you only need side effects like logging. As MDN notes, forEach() always returns undefined and is not chainable.
No. map(), filter(), and reduce() all return new values without modifying the original array. This non-mutating behavior makes them ideal for functional programming patterns. However, if your callback modifies objects within the array, those mutations will affect the originals since objects are passed by reference.
Use reduce() when you need to accumulate array elements into a single value — sums, counts, grouped objects, or flattened arrays. For simple aggregations, reduce() is more concise. For complex logic with multiple steps, a for loop can be more readable. The MDN reduce documentation provides detailed examples for both simple and advanced use cases.
Yes. Because map() and filter() return arrays, you can chain them: arr.filter(...).map(...).reduce(...). Each method processes the result of the previous one. This chaining pattern creates readable data pipelines, though be mindful that each method creates an intermediate array.
If the array has elements, reduce() uses the first element as the initial accumulator and starts iteration from the second element. If the array is empty, it throws a TypeError. The ECMAScript specification requires this behavior. Always provide an initial value to avoid unexpected crashes.


Reference

Articles

Videos

Last modified on February 17, 2026