forEach or map: which one should I use?
Use map to build a new array and forEach for side effects. Common smells, how to stop a loop early, and why forEach doesn't wait for async callbacks.
Use map() when you want a new array built from the old one. Use forEach() when you want to do something with each item, like logging it, saving it or updating the DOM, and don't need a result. map() returns a new array of the same length; forEach() always returns undefined.
const prices = [10, 20, 30];
const doubled = prices.map((price) => price * 2);
console.log(doubled); // [20, 40, 60]
prices.forEach((price) => console.log(price)); // 10, 20, 30
const result = prices.forEach((price) => price * 2);
console.log(result); // undefinedNeither one changes the original array by itself.
Two common smells
Using map() only as a loop, with the new array thrown away. It works, but it tells the reader you wanted a result:
// the returned array is never used
items.map((item) => save(item));
// say what you mean
items.forEach((item) => save(item));The opposite: forEach() pushing into an array declared outside. That's map() or filter() written the long way:
const activeNames = [];
users.forEach((user) => {
if (user.active) activeNames.push(user.name);
});The same result, without the empty array and the mutation:
const activeNames = users
.filter((user) => user.active)
.map((user) => user.name);One more map() gotcha: it creates a new array, but not new objects. If the callback changes user.active and returns user, the original objects change too. Return a copy instead: users.map((user) => ({ ...user, active: true })).
Neither can stop early
return inside a forEach() callback only skips the current item, and there's no break:
[1, 2, 3].forEach((n) => {
if (n === 2) return; // skips 2, the loop goes on
console.log(n);
});
// 1
// 3To stop early, use for...of with break, or a method that stops by itself:
for (const user of users) {
if (user.banned) break;
sendInvite(user);
}
const admin = users.find((user) => user.role === 'admin');
const hasAdmin = users.some((user) => user.role === 'admin');Async callbacks
forEach() doesn't wait for promises. It calls every callback, ignores the promises they return, and the code after it runs right away. Errors inside those callbacks aren't caught by a surrounding try...catch either.
urls.forEach(async (url) => {
await fetch(url);
});
console.log('done'); // logs before any fetch finishesTo go one by one, use for...of with await. To run them in parallel and wait for all, use map() with Promise.all():
// one after another
for (const url of urls) {
await fetch(url);
}
// in parallel
const responses = await Promise.all(
urls.map((url) => fetch(url))
);An async callback in map() without Promise.all() gives you an array of promises, not results.
Performance
It doesn't matter in practice. The difference between map(), forEach() and a for loop is too small to notice in normal code, so pick the one that says what you mean.
React
Rendering a list in JSX uses map(), because JSX needs the array of elements it returns. forEach() returns undefined, so nothing would render:
function UserList({ users }) {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}For the key, use an ID from your data. Here's why you shouldn't use the index as a key.