albert.walickihire me
← all solutions
javascript

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); // undefined

Neither 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
// 3

To 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 finishes

To 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.

more solutions
work with me

Got something that needs building?

Frontend builds, full-stack features in Django, design-system work. Available for work.

See my workGet in touch