map vs forEach in JavaScript
Short answer
map returns a new array containing whatever your callback returned for each element. forEach returns undefined and exists purely to run side effects. Use map when you're transforming data into something new, forEach when you're doing something with each item and don't need a result.
| map | forEach | |
|---|---|---|
| Returns | A new array, same length | undefined |
| Purpose | Transform data | Side effects |
| Chainable | Yes — .map().filter().join() | No |
| Mutates the original | No | No (but your callback can) |
| Can you break out? | No | No |
| Awaits async callbacks? | No | No |
| Works in JSX | Yes — returns elements | No — renders nothing |
The one-line difference
Both walk the array and call your function once per element with the same arguments. The only difference is what happens to your callback's return value: map collects it into a new array, forEach throws it away.
1const nums = [1, 2, 3];
2
3const doubled = nums.map(n => n * 2);
4// → [2, 4, 6]
5
6const nothing = nums.forEach(n => n * 2);
7// → undefined (the * 2 results are discarded)
8
9console.log(nums);
10// → [1, 2, 3] (neither mutates the original)That single difference decides everything else. Because map produces a value it can be chained; because forEach produces nothing it can only be the end of a statement.
1const names = users
2 .filter(u => u.active)
3 .map(u => u.name)
4 .sort();
5// → ["Arun", "Divya", "Karthik"]
6
7// forEach breaks the chain — there's nothing to call .sort() on
8users.forEach(u => u.name).sort();
9// → TypeError: Cannot read properties of undefined (reading 'sort')Choosing between them
The honest rule: if you are building a new value from the old one, use map. If you are doing something to the outside world — logging, saving, updating the DOM, pushing to an external array — use forEach.
// map — transforming
const prices = items.map(i => i.price * 1.18); // → [118, 236]
// forEach — side effects
orders.forEach(o => sendConfirmationEmail(o)); // → undefined, emails sentYou cannot break out of either
This is the first thing interviewers probe past the definition. Neither map nor forEach supports break or continue — those are statements, and the callback is a separate function. A return inside the callback only ends that one iteration.
1[1, 2, 3, 4].forEach(n => {
2 if (n === 3) return; // acts like "continue", NOT "break"
3 console.log(n);
4});
5// → 1
6// → 2
7// → 4 (it kept going)
8
9[1, 2, 3].forEach(n => {
10 if (n === 2) break;
11});
12// → SyntaxError: Illegal break statementIf you need to stop early, pick the tool built for it:
1// for...of — full break/continue support
2for (const n of nums) {
3 if (n > 3) break;
4 console.log(n);
5}
6
7// some — stops at the first true, and reads as intent
8const hasNegative = nums.some(n => n < 0); // → false
9
10// find — stops at the first match and returns it
11const admin = users.find(u => u.role === "admin"); // → { name: "Arun", … }The async trap: forEach does not wait
This is the highest-value thing on this page and the reason the question keeps getting asked. forEach ignores the promise your async callback returns, so it starts every iteration and returns immediately — your code carries on before any of the work has finished.
1async function saveAll(items) {
2 items.forEach(async (item) => {
3 await save(item);
4 console.log("saved", item.id);
5 });
6 console.log("done");
7}
8
9await saveAll([{id:1},{id:2}]);
10// → done ← printed FIRST, nothing has been saved yet
11// → saved 1
12// → saved 2The fix depends on whether you want the work sequential or parallel — and knowing both is what makes this a good interview answer.
1// Sequential — one after another, order guaranteed
2for (const item of items) {
3 await save(item);
4}
5console.log("done"); // → runs last, correctly
6
7// Parallel — all at once, much faster when they're independent
8await Promise.all(items.map(item => save(item)));
9console.log("done"); // → runs last, correctlyNotice that the parallel fix uses map, not forEach — precisely because map returns the array of promises that Promise.all needs. It is the cleanest demonstration of why the return value matters.
This is 1 of 80+ questions in the JavaScript Interview Kit
Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the JavaScript Interview Kit → ₹299When forEach is genuinely the right call
forEach gets a slightly bad reputation from people who over-correct after learning about map, but there are cases where it is clearly the better choice — and saying so shows judgement rather than dogma.
1// Populating an external structure
2const byId = new Map();
3users.forEach(u => byId.set(u.id, u)); // → Map { 1 => {…}, 2 => {…} }
4
5// Attaching listeners — nothing to collect
6buttons.forEach(b => b.addEventListener("click", onClick));
7
8// Fire-and-forget side effects
9errors.forEach(e => logger.warn(e.message));
10
11// Walking without transforming
12Object.entries(config).forEach(([k, v]) => console.log(`${k}=${v}`));
13// → theme=dark
14// → grid=trueEach of those produces nothing, and using map instead would build a throwaway array of undefined. The linter would flag it, and more importantly the reader would pause to work out what the returned array was for — which is a small tax on everyone who touches the code afterwards.
In React, only map works
Rendering a list in JSX means producing elements, and JSX renders whatever expression you give it. forEach returns undefined, and React renders nothing for undefined — so the list silently disappears with no error.
1// Renders nothing at all, no warning
2<ul>{items.forEach(i => <li key={i.id}>{i.name}</li>)}</ul>
3// → <ul></ul>
4
5// Correct
6<ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>
7// → <ul><li>…</li><li>…</li></ul>The map patterns you'll actually write
Beyond the textbook n => n * 2, a handful of map idioms come up constantly in real frontend work — and in take-home tests.
1// Pluck a field
2users.map(u => u.name); // → ["Arun", "Divya"]
3
4// Reshape for a <select>
5cities.map(c => ({ value: c.id, label: c.name }));
6
7// Use the index
8items.map((item, i) => `${i + 1}. ${item}`); // → ["1. a", "2. b"]
9
10// Add a derived field without mutating
11rows.map(r => ({ ...r, total: r.price * r.qty }));
12
13// Build a fixed-length range
14Array.from({ length: 3 }, (_, i) => i); // → [0, 1, 2]For the reverse direction — collapsing an array into a lookup object — reduce is the right tool, though Object.fromEntries paired with map is often more readable:
1const users = [{ id: 1, name: "Arun" }, { id: 2, name: "Divya" }];
2
3Object.fromEntries(users.map(u => [u.id, u]));
4// → { 1: {id:1,…}, 2: {id:2,…} } ← map does the transform, fromEntries the shape
5
6users.reduce((acc, u) => ({ ...acc, [u.id]: u }), {});
7// → same result, but spreading acc each iteration makes it O(n²) — avoidPerformance, honestly
A classic follow-up is "which is faster?", and the honest answer is that it does not matter for the arrays you actually have. A plain for loop is marginally faster than both because it avoids a function call per element, and forEach is marginally faster than map because it doesn't allocate a new array. On ten thousand items you are talking about a fraction of a millisecond.
The right answer in an interview is that readability wins until you have measured a problem, and that if you are iterating an array large enough for the difference to register, the real fix is usually virtualisation or pagination rather than a different loop.
| Method | Use when |
|---|---|
| map | Transforming every element into a new array |
| forEach | Side effects, no result needed |
| for...of | You need break/continue, or await inside the loop |
| filter | Selecting a subset |
| reduce | Collapsing to a single value |
| find / some / every | Answering a question and stopping early |
What arguments does the callback receive?
Both pass (element, index, array), and both accept an optional second argument to bind `this`. This is why items.map(parseInt) is a classic trap: parseInt receives the index as its radix, so ["1","2","3"].map(parseInt) gives [1, NaN, NaN].
Do they skip holes in sparse arrays?
Yes, both skip empty slots — the callback never runs for them. But map preserves the holes in its output, so [1, , 3].map(n => n * 2) returns [2, <1 empty item>, 6] rather than a dense array. It's a rare edge case, but a good one to know exists.
Can you mutate the array while iterating?
You can, but you shouldn't. The range of elements to visit is fixed before the first callback runs, so elements appended during iteration are never visited, and deleting elements causes others to be skipped. Build a new array instead.
Is map immutable?
map never mutates the array it's called on, but it doesn't deep-clone anything either. If your callback returns the same object references, the new array holds the same objects — mutating one of them still affects the original. map gives you a new array, not new contents.
Can map change the length of the array?
No — map always returns an array of exactly the same length as the input. If you need fewer elements, filter first or chain .filter() after the map. If you need more, use flatMap, which lets each callback return an array that gets flattened one level into the result.
What is flatMap and when would you use it?
flatMap is map followed by a single level of flattening. It's the clean way to expand or drop elements in one pass: return an array of several items to expand, or an empty array to remove the element entirely. tags.flatMap(t => t.split(",")) turns ["a,b", "c"] into ["a", "b", "c"].
Frequently asked questions
- What is the difference between map and forEach in JavaScript?
- map returns a new array built from whatever your callback returns for each element. forEach returns undefined and exists to run side effects. Both iterate identically and neither mutates the original array — the only real difference is whether the callback's return value is kept.
- When should I use map instead of forEach?
- Use map when you're turning the array into a new array — extracting a field, formatting values, producing JSX elements. Use forEach when you're doing something with each item and don't need a result, like logging or sending requests. If you aren't using map's return value, forEach is the correct choice.
- Can you break out of a map or forEach loop?
- No. break is a syntax error inside the callback because the callback is a separate function, and return only ends the current iteration — it behaves like continue. Use for...of when you need break, or some, every or find when you want to stop at the first match.
- Why doesn't forEach work with async/await?
- forEach ignores the promise an async callback returns, so it starts every iteration and returns immediately without waiting. Use for...of with await for sequential work, or await Promise.all(items.map(fn)) to run them in parallel.
- Is map faster than forEach?
- forEach is marginally faster because it doesn't allocate a result array, and a plain for loop is marginally faster than both. The differences are negligible at realistic array sizes — choose based on which one expresses your intent, and only optimise after measuring.
- Does map mutate the original array?
- No, map always returns a new array. But it doesn't clone the elements: if the callback returns the same object references, both arrays contain the same objects, so mutating one still affects the other. map gives you a new array, not new contents.
- Why does forEach render nothing in React?
- Because it returns undefined, and React renders nothing for undefined. The list silently disappears with no error, which makes it a confusing bug. Always use map to render lists in JSX, and don't forget the return if your callback has a block body.
- What arguments do the map and forEach callbacks receive?
- Both receive (element, index, array) and accept an optional thisArg. Passing a function that takes more than one parameter can bite you: ["1","2","3"].map(parseInt) returns [1, NaN, NaN] because parseInt reads the index as its radix.
- What is the difference between map and for...of?
- map builds a new array and cannot be exited early. for...of is a statement, so it supports break and continue and lets you await inside the loop body. Use map for transformation and for...of when you need control flow or sequential async work.
- Do map and forEach skip empty array slots?
- Yes — in a sparse array like [1, , 3] the callback never runs for the hole. map preserves the hole in its output rather than producing a dense array, so the result is [2, <1 empty item>, 6].
- Is map or forEach different in TypeScript?
- The runtime behaviour is identical; TypeScript only adds typing. map infers the new array's element type from the callback's return type, so mapping User[] to a string return gives you string[]. forEach is typed as returning void, which is why using its result is a compile error rather than a silent bug.
- Should I use reduce instead of map?
- Only when you're collapsing the array into a single value — a total, a lookup object, a grouped structure. Using reduce to build an array that map could produce is harder to read for no benefit. Reach for the most specific method that does the job.
This is 1 of 80+ questions in the JavaScript Interview Kit
Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the JavaScript Interview Kit → ₹299