By Rahul — Google Frontend Engineer
The One Rule
If you need the result array, use map(). If you do not, use forEach(). That is it. But let me show you why people get this wrong and what happens in production.
Core Difference
const numbers = [1, 2, 3, 4, 5];
// map returns a NEW array
const doubled = numbers.map(n => n * 2);
// doubled = [2, 4, 6, 8, 10]
// forEach returns undefined
const result = numbers.forEach(n => console.log(n));
// result = undefinedCommon Mistakes
Mistake 1: Using map When You Do Not Need the Result
// BAD — creates an array and throws it away
users.map(user => {
sendEmail(user.email);
});
// GOOD
users.forEach(user => {
sendEmail(user.email);
});ESLint rule array-callback-return catches this. Enable it.
Mistake 2: Using forEach to Build an Array
// BAD — manual push
const names = [];
users.forEach(user => {
names.push(user.name);
});
// GOOD — map does this naturally
const names = users.map(user => user.name);Mistake 3: Trying to Break Out of forEach
// This does NOT work
[1, 2, 3, 4, 5].forEach(n => {
if (n === 3) return; // Only skips this iteration
// break; — SyntaxError!
console.log(n);
});
// Logs: 1, 2, 4, 5
// Use for...of if you need to break
for (const n of [1, 2, 3, 4, 5]) {
if (n === 3) break;
console.log(n);
}
// Logs: 1, 2Performance
In V8, for loop is fastest, forEach and map have similar overhead. For arrays under 10,000 items, the difference is negligible. For very large datasets, consider for loops or streaming approaches.
Async Gotcha
// BAD — fires all requests simultaneously, does not await
urls.forEach(async (url) => {
await fetch(url); // This does NOT wait
});
// GOOD — sequential
for (const url of urls) {
await fetch(url);
}
// GOOD — parallel
await Promise.all(urls.map(url => fetch(url)));This is one of the most common production bugs. forEach does not respect async/await. The callback fires and forEach moves on immediately.
Best Practices
- Use
mapfor transformations (data in → data out) - Use
forEachfor side effects (logging, API calls, DOM mutations) - Never use
forEachwith async callbacks — usefor...oforPromise.allwithmap - Enable
array-callback-returnESLint rule
Summary
map transforms. forEach performs side effects. The async gotcha alone makes this worth understanding deeply.