Learn map, reduce, and filter in JavaScript. Transform, filter, and combine arrays without mutation. Includes method chaining and common pitfalls.
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.
Copy
Ask AI
// The power of map, filter, and reduce in actionconst 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 upconsole.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!
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:
Copy
Ask AI
┌─────────────────────────────────────────────────────────────────────────┐│ 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 Immutability Principle: None of these methods change the original array. They always return something new. This makes your code predictable and easier to debug.
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.
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!
Copy
Ask AI
// 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:
Copy
Ask AI
// Option 1: Wrap in arrow functionconst 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
Copy
Ask AI
const numbers = [1, 2, 3]// map: When you need a new arrayconst doubled = numbers.map(n => n * 2)console.log(doubled) // [2, 4, 6]// forEach: When you just want to do something with each itemnumbers.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 effectsnumbers.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.
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.
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
Copy
Ask AI
const numbers = [1, 2, 3, 4, 5]// filter: Get ALL even numbersnumbers.filter(n => n % 2 === 0) // [2, 4]// find: Get the FIRST even numbernumbers.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
Copy
Ask AI
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 oneconst result = users.filter(u => u.id === 2)[0] // Checks all elements// ✓ EFFICIENT: Use find for single item lookupconst user = users.find(u => u.id === 2) // Stops at first match// ❌ WASTEFUL: Using filter just to check existenceconst hasAdmin = users.filter(u => u.admin).length > 0// ✓ BETTER: Use some for existence checkconst 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 = [5, 2, 9, 1, 7]const max = numbers.reduce((acc, n) => n > acc ? n : acc, numbers[0])console.log(max) // 9const 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)) // 9console.log(Math.min(...numbers)) // 1
Modern Alternative (ES2024): JavaScript now has Object.groupBy() which does this in one line:
Copy
Ask AI
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+).
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.
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.
When you see a chain, read it like a data pipeline. Data flows from top to bottom, transformed at each step:
Copy
Ask AI
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
Each method in a chain iterates over the array. For small arrays, this doesn’t matter. For large arrays, consider combining operations:
Copy
Ask AI
const hugeArray = Array.from({ length: 100000 }, (_, i) => i)// ❌ SLOW: Three separate iterationsconst 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 reduceconst 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.
┌─────────────────────────────────────────────────────────────────────────┐│ 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.
For filter, you need a two-step approach since filter expects a boolean, not a Promise:
Copy
Ask AI
const numbers = [1, 2, 3, 4, 5]// Check if a number is "valid" via async operationasync function isValid(n) { // Imagine this calls an API return n % 2 === 0}// ❌ WRONG: filter doesn't awaitconst 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 filterconst 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.
Question 1: What does map() return if the callback doesn't return anything?
Answer:An array of undefined values. In JavaScript, functions without an explicit return statement return undefined.
Copy
Ask AI
const numbers = [1, 2, 3]// Missing returnconst 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).
Question 2: What's the difference between filter() and find()?
Answer:
filter() returns an array of all matching elements (could be empty)
find() returns the first matching element (or undefined if none)
Copy
Ask AI
const numbers = [1, 2, 3, 4, 5, 6]numbers.filter(n => n % 2 === 0) // [2, 4, 6] — All matchesnumbers.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.
Question 3: Why does reduce() crash on empty arrays without an initial value?
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.
Copy
Ask AI
// 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.
Question 4: What's wrong with this code?
Copy
Ask AI
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.
What is the difference between map() and forEach() in JavaScript?
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.
Does map() mutate the original array?
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.
When should I use reduce() instead of a for loop?
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.
Can I chain map(), filter(), and reduce() together?
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.
What happens if I forget the initial value in reduce()?
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.