Core Concepts7 min read · Updated 2026-03-10

map() vs forEach() in JavaScript: Which One to Use and Why It Matters

Both iterate over arrays. But they have completely different purposes. Choosing the wrong one doesn't just make your code less readable — it leads to bugs when the return value matters.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

Both map() and forEach() loop over every element in an array and call a callback. That's where the similarity ends. They have different return values, different intentions, and using one where you meant the other produces either wrong results or confusing code.

The One-Line Difference

forEach — do something with each element. Returns undefined. For side effects only.

map — transform each element into something new. Returns a new array. For data transformation.

const numbers = [1, 2, 3, 4, 5]

// forEach: iterate and do something — returns undefined numbers.forEach(n => console.log(n * 2)) // logs 2, 4, 6, 8, 10 const result1 = numbers.forEach(n => n * 2) result1 // undefined — forEach always returns undefined

// map: transform and collect — returns a new array const doubled = numbers.map(n => n * 2) doubled // [2, 4, 6, 8, 10] — new array, original untouched numbers // [1, 2, 3, 4, 5] — unchanged

Why Return Value Matters

The return value is the entire design difference. map collects the return value of each callback into a new array. forEach discards the return value of each callback — whatever you return from a forEach callback is thrown away.

// ❌ Common mistake: using forEach expecting a result
const names = ['alice', 'bob', 'charlie']

const capitalized = names.forEach(name => name.charAt(0).toUpperCase() + name.slice(1) )

capitalized // undefined — forEach never returns the transformed values

// ✓ Correct: use map when you need the transformed array
const capitalized = names.map(name =>
  name.charAt(0).toUpperCase() + name.slice(1)
)

capitalized // ['Alice', 'Bob', 'Charlie']

This is the #1 forEach bug in real codebases: using it when you need map, wondering why the result is always undefined.

When to Use forEach

forEach is correct when the loop's purpose is side effects — when you want to do something with each element rather than transform it into a new value.

const products = [
  { id: 1, name: 'Laptop', price: 999 },
  { id: 2, name: 'Phone', price: 599 },
]

// Side effects — updating external state, not producing a new array products.forEach(product => { analytics.track('product_viewed', { id: product.id }) })

// Side effects — writing to DOM products.forEach(product => { const li = document.createElement('li') li.textContent = product.name list.appendChild(li) })

// Side effects — logging products.forEach((product, index) => { console.log(${index + 1}. ${product.name}: $${product.price}) })

In all these cases, you don't need a returned array. You're performing an action. forEach is semantically correct.

When to Use map

map is correct when you're transforming data — when you want a new array where each element corresponds to a transformed version of the original.

const users = [
  { id: 1, firstName: 'Alice', lastName: 'Smith' },
  { id: 2, firstName: 'Bob',   lastName: 'Jones' },
]

// Transform: create a new array of full names const fullNames = users.map(u => ${u.firstName} ${u.lastName}) // ['Alice Smith', 'Bob Jones']

// Transform: extract specific fields const ids = users.map(u => u.id) // [1, 2]

// Transform: reshape objects const displayUsers = users.map(u => ({ id: u.id, name: ${u.firstName} ${u.lastName}, })) // [{ id: 1, name: 'Alice Smith' }, { id: 2, name: 'Bob Jones' }]

// Transform: chain with filter const expensiveNames = products .filter(p => p.price > 800) .map(p => p.name) // ['Laptop']

The Chainability Difference

map returns an array, which means you can chain it directly with other array methods. forEach returns undefined, so chaining breaks:

const numbers = [1, 2, 3, 4, 5, 6]

// ✓ map chains naturally const result = numbers .filter(n => n % 2 === 0) // [2, 4, 6] .map(n => n * n) // [4, 16, 36] .reduce((sum, n) => sum + n, 0) // 56

// ❌ forEach breaks chains — nothing to chain onto const broken = numbers .filter(n => n % 2 === 0) .forEach(n => n * n) // returns undefined .reduce(...) // TypeError: Cannot read properties of undefined

The moment you want to chain array methods, map is the right tool.

Mutation Warning

Neither map nor forEach should mutate the original array. map is designed for immutable transformation — it produces a new array. forEach doesn't return anything, so mutating inside it is the only way to "produce a result," which is an antipattern.

const items = [{ val: 1 }, { val: 2 }, { val: 3 }]

// ❌ Antipattern: mutating objects inside map const mutated = items.map(item => { item.val *= 2 // mutates the original object return item }) // items is now also changed — this defeats the purpose of map

// ✓ Correct: map produces genuinely new objects const transformed = items.map(item => ({ ...item, val: item.val * 2 })) // items is untouched, transformed is a new array of new objects

Performance: Is There a Difference?

Practically, no. Both iterate every element once — O(n). The overhead difference is immeasurable for typical array sizes. Don't choose between them based on performance.

Choose based on intention:

If performance actually matters (millions of items), benchmark with your actual data. At that scale, a plain for loop is faster than both because it has no callback overhead.

Quick Decision Guide

Need a new array of transformed values?       → map()
Need to chain with filter/reduce/etc?         → map()
Performing side effects (DOM, logging, API)?  → forEach()
Don't need a return value?                    → forEach()

The Real Cost of Getting This Wrong

// This bug exists in production codebases everywhere:
function getDisplayNames(users) {
  return users.forEach(u => u.displayName)  // always returns undefined
  //     ↑ should be .map()
}

const names = getDisplayNames(users) names.length // TypeError: Cannot read properties of undefined (reading 'length')

The error appears far from the cause — a null check fails, a component crashes on render, an API returns an unexpected shape — and the root is forEach where map was needed.

Use map when you care about what the callback returns. Use forEach when you don't.

📚 Practice These Topics
Arrays
8–12 questions
Higher order functions
5–8 questions
Callbacks
3–5 questions

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Core Concepts
Arrow Functions vs Regular Functions in JavaScript: 6 Key Differences
9 min read
Interview Prep
Promise.all vs allSettled vs race vs any: The Complete Comparison
9 min read
Core Concepts
null vs undefined in JavaScript: What They Mean, When They Appear, and How to Handle Each
7 min read