JS Array Map vs forEach: Which Should You Use?
Compare JavaScript's map() and forEach() methods with clear examples, performance benchmarks, and decision criteria. Learn when each method is the right choice for iteration, transformation, and side effects.
Both map() and forEach() iterate over every element in an array and run a callback function on each one. The difference is what they do with the result. map() collects return values into a new array. forEach() ignores return values and returns undefined. This single distinction determines which method you should reach for in every situation.
The Core Difference
const prices = [10, 20, 30, 40, 50];
// map(): returns a NEW array of transformed values
const withTax = prices.map(price => price * 1.08);
console.log(withTax); // [10.8, 21.6, 32.4, 43.2, 54]
// forEach(): returns undefined, used for side effects
prices.forEach(price => {
console.log(`Price: $${price.toFixed(2)}`);
});
// Price: $10.00
// Price: $20.00
// ...Side-by-Side Comparison
| Feature | map() | forEach() |
|---|---|---|
| Returns | New array | undefined |
| Purpose | Transform data | Execute side effects |
| Chainable | Yes (.map().filter().reduce()) | No (returns undefined) |
| Modifies original | No | No (but callback can mutate objects) |
| Async/await support | Does not await promises | Does not await promises |
| Early exit (break) | Not supported | Not supported |
| Skips empty slots | Yes | Yes |
| Callback return value | Collected into output array | Ignored |
When to Use map()
Use map() when you need to transform each element and collect the results into a new array.
Transforming Data
const users = [
{ firstName: "Alice", lastName: "Chen" },
{ firstName: "Bob", lastName: "Smith" },
{ firstName: "Carol", lastName: "Johnson" },
];
const fullNames = users.map(user => `${user.firstName} ${user.lastName}`);
console.log(fullNames); // ["Alice Chen", "Bob Smith", "Carol Johnson"]Building UI Components
const menuItems = [
{ label: "Home", href: "/" },
{ label: "About", href: "/about" },
{ label: "Contact", href: "/contact" },
];
const navLinks = menuItems.map(item =>
`<a href="${item.href}">${item.label}</a>`
);
console.log(navLinks.join("\n"));Chaining with Other Methods
The real power of map() is chaining. Because it returns an array, you can pipe results directly into filter(), reduce(), or another map():
const orders = [
{ product: "Laptop", price: 999, quantity: 1 },
{ product: "Cable", price: 12.99, quantity: 5 },
{ product: "Mouse", price: 49, quantity: 2 },
{ product: "Monitor", price: 349, quantity: 1 },
];
const expensiveItemNames = orders
.filter(order => order.price * order.quantity > 50)
.map(order => order.product);
console.log(expensiveItemNames); // ["Laptop", "Mouse", "Monitor"]When to Use forEach()
Use forEach() when you need to do something with each element without building a new array.
Logging and Debugging
const steps = ["Initialize", "Connect", "Authenticate", "Fetch", "Render"];
steps.forEach((step, index) => {
console.log(`[Step ${index + 1}] ${step}`);
});Updating Existing Objects
const inventory = [
{ sku: "A1", name: "Widget", stock: 100 },
{ sku: "B2", name: "Gadget", stock: 0 },
{ sku: "C3", name: "Gizmo", stock: 45 },
];
inventory.forEach(item => {
item.status = item.stock > 0 ? "available" : "out-of-stock";
});
console.log(inventory[1].status); // "out-of-stock"Populating External Data Structures
const records = [
{ id: "u1", name: "Alice" },
{ id: "u2", name: "Bob" },
{ id: "u3", name: "Carol" },
];
const lookup = {};
records.forEach(record => {
lookup[record.id] = record.name;
});
console.log(lookup); // { u1: "Alice", u2: "Bob", u3: "Carol" }The Wasted Array Anti-Pattern
The most common mistake is using map() when you only need side effects. This creates a new array that is immediately discarded:
const users = ["Alice", "Bob", "Carol"];
// Anti-pattern: map() return value is thrown away
users.map(user => {
console.log(`Welcome, ${user}!`);
});
// Creates ["undefined", "undefined", "undefined"] and discards it
// Correct: forEach() when you only need side effects
users.forEach(user => {
console.log(`Welcome, ${user}!`);
});Do Not Ignore map() Output
If you use map() but never assign or use the returned array, switch to forEach(). Linters like ESLint flag this with the array-callback-return rule. Creating and discarding arrays wastes memory and confuses other developers reading your code.
Performance Comparison
For most applications, the speed difference between map() and forEach() is irrelevant. Both are O(n). The real difference is in memory allocation:
const data = Array.from({ length: 100000 }, (_, i) => i);
// map(): allocates a new 100,000-element array
console.time("map");
const mapped = data.map(n => n * 2);
console.timeEnd("map"); // ~3-5ms
// forEach(): no new array allocated
console.time("forEach");
data.forEach(n => n * 2);
console.timeEnd("forEach"); // ~2-4ms| Metric | map() | forEach() |
|---|---|---|
| Time complexity | O(n) | O(n) |
| Memory | Allocates new array (O(n)) | No allocation |
| Garbage collection | New array needs GC if unused | Nothing extra to collect |
| Practical difference | Negligible for arrays under 100K | Negligible for arrays under 100K |
Neither map() nor forEach() for These Cases
Both methods share limitations that make neither one the right choice in certain situations:
Need to Break Early
const scores = [92, 85, 78, 95, 88, 70, 45, 82];
// Neither map nor forEach can break early
// Use for...of or find() instead
let firstFailing;
for (const score of scores) {
if (score < 50) {
firstFailing = score;
break;
}
}
// Or use find()
const firstFailing2 = scores.find(s => s < 50); // 45Need Sequential Async Operations
const files = ["config.json", "data.csv", "report.pdf"];
// Both map and forEach fire all callbacks synchronously
// Use for...of for sequential async
async function processFiles() {
for (const file of files) {
await uploadFile(file);
console.log(`Uploaded: ${file}`);
}
}Need Both Transformation and Side Effects
const items = [
{ name: "Widget", price: 25, discount: 0.1 },
{ name: "Gadget", price: 50, discount: 0.2 },
];
// Don't use map() with side effects mixed in
// Instead, separate concerns
const finalPrices = items.map(item => ({
name: item.name,
finalPrice: item.price * (1 - item.discount),
}));
// Then use forEach for side effects
finalPrices.forEach(item => {
console.log(`${item.name}: $${item.finalPrice.toFixed(2)}`);
});Decision Flowchart
Use this mental model when choosing between map(), forEach(), and other iteration methods:
Do you need a new array?
├── YES → Use map()
│ Can you chain it with filter/reduce? → Even better
└── NO
├── Do you need to stop early?
│ ├── YES → Use for...of or for loop
│ └── NO
│ ├── Do you need async/await?
│ │ ├── YES → Use for...of
│ │ └── NO → Use forEach()
│ └── Do you need a single value?
│ └── YES → Use reduce()
Common Mistakes
Using map() for logging only:
const items = ["a", "b", "c"];
// Bad: creates and discards an array
items.map(item => console.log(item));
// Good: forEach for pure side effects
items.forEach(item => console.log(item));Chaining after forEach():
const nums = [1, 2, 3];
// TypeError: Cannot read properties of undefined
// nums.forEach(n => n * 2).filter(n => n > 2);
// Fix: use map() when you need to chain
nums.map(n => n * 2).filter(n => n > 2); // [4, 6]Confusing return behavior:
const nums = [1, 2, 3, 4, 5];
// forEach return is ignored
const result = nums.forEach(n => {
return n > 3; // This return does nothing useful
});
console.log(result); // undefined
// Use filter() when you need to select elements by condition
const big = nums.filter(n => n > 3);
console.log(big); // [4, 5]Best Practices
- Default to map() for data transformations. Any time you need a new array derived from the original, map() is the right choice.
- Use forEach() only for side effects. Logging, mutations, sending events, populating external structures.
- Never ignore the map() return value. If you are not using the new array, switch to forEach().
- Separate transformation from side effects. Do not mix
console.log()inside amap()callback. Map first, then forEach the results. - Consider reduce() for accumulation. When you need a single value (sum, grouped object, count), reduce() is more expressive than building a result with forEach().
Rune AI
Key Insights
- map() returns a new array: use it for transforming data and chaining with filter/reduce
- forEach() returns undefined: use it for side effects like logging, mutations, and external writes
- Never discard map() output: if you ignore the returned array, switch to forEach()
- Neither supports break or async/await: use for...of or find() when you need early exit or sequential async
- Performance is equal: choose based on intent (transformation vs side effects), not speed
Frequently Asked Questions
Can I use map() and forEach() on the same array?
Is map() always better than forEach()?
Do both methods skip empty slots in sparse arrays?
Can I make either method work with async/await?
Which one is faster?
Conclusion
The choice between map() and forEach() comes down to one question: do you need a new array? If yes, use map(). If you only need to execute side effects on each element, use forEach(). Never use map() when the return value is discarded, and never use forEach() when you need to chain or collect results. For cases where neither fits (early exit, async operations, single-value accumulation), reach for for...of, find(), or reduce().
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.