Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Choose map() when every visited array element should become a value in a new array. Choose forEach() when you need an action for each present element and no result collection. Choose a classic for loop when index control, break, continue, or ordered asynchronous work matters.

These constructs are different contracts, not interchangeable spellings. The useful question is what your code must return, whether it must stop early, and whether each operation has to wait for the previous one.

Quick decision: for, forEach(), or map()?

What your code needs Best fit Why What it returns
Build a transformed array map() Each callback result is placed at the corresponding position in a new array. A new array
Perform an action for every present element forEach() The callback is for side effects; its return value is discarded. undefined
Use an index explicitly, stop with break, or skip with continue for Loop statements expose normal control flow and index management. Whatever you return from the surrounding function, or no value if you do not return
Await each operation before starting the next An await-capable loop, such as for...of or a classic for inside an async function The loop can pause at await. A promise from the surrounding async function
Stop when a condition is met some(), find(), or findIndex() These array methods have short-circuit behavior; a loop also works when you need more control. Method-specific result, such as a boolean, element, or index

Use the construct whose contract matches the outcome. Do not use map() merely because it is concise if you will throw away its returned array, and do not use forEach() when the operation must stop or wait.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How map() works

map() is a transformation operation. It calls its callback for each assigned array index and stores the callback’s result in a new array at the corresponding index. The original array is not replaced by that operation.

const prices = [10, 20, 30];
const withTax = prices.map((price) => price * 1.1);

console.log(withTax); // [11, 22, 33]
console.log(prices);  // [10, 20, 30]

The returned array is the point of using map(). If you need to retain the transformed values, assign the result, return it from a function, or pass it to another operation:

function addTax(prices, rate) {
  return prices.map((price) => price * (1 + rate));
}

const totals = addTax([10, 20, 30], 0.1);

MDN describes calling map() without using its returned array as an anti-pattern and recommends forEach() or for...of for side effects. A callback can return any value, including an object, string, boolean, or promise; that value becomes an element in the new array.

Returning objects from map()

When an arrow function returns an object literal, wrap the object in parentheses so JavaScript parses it as an expression:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const users = [
  { id: 1, name: 'Ada' },
  { id: 2, name: 'Lin' }
];

const labels = users.map((user) => ({
  id: user.id,
  label: user.name.toUpperCase()
}));

The result is a separate array of new objects. If your callback mutates an object from the original array, the object itself is still shared; map() does not deep-clone values.

When map() is the wrong tool

This code computes an array and then discards it:

prices.map((price) => console.log(price));

It works mechanically, but it communicates the wrong intent and allocates a result that is not needed. Use:

prices.forEach((price) => console.log(price));

or use a loop when you also need control statements.

How forEach() works

forEach() visits each assigned array index in order and calls your callback. It is designed for side effects such as logging, updating an external accumulator, rendering, or sending a notification. The method itself returns undefined, and whatever the callback returns is discarded.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const prices = [10, 20, 30];

prices.forEach((price, index) => {
  console.log(`Item ${index}: $${price}`);
});

There is no output array to capture:

const result = prices.forEach((price) => price * 1.1);
console.log(result); // undefined

If you need an accumulated result, use map() for one output per input, a reducer for a single aggregate, or an explicit loop with an accumulator. Do not expect a value returned from the callback to escape through forEach().

Use forEach() for clear side effects

const messages = ['Saved', 'Queued', 'Sent'];

messages.forEach((message) => {
  analytics.track('status_seen', { message });
});

This reads as “perform this action for every present element.” Keep the callback focused; if it becomes responsible for filtering, accumulating, error recovery, and multiple control branches, a loop or a chain of purpose-built array methods may be easier to verify.

How a classic for loop works

A classic for statement gives you explicit initialization, a condition, and an update expression:

for (let i = 0; i < prices.length; i += 1) {
  console.log(prices[i]);
}

That extra ceremony is useful when the index itself matters, when you must compare neighboring elements, when you need to mutate a counter by more than one step, or when control flow can end the loop early.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

break exits immediately

for (let i = 0; i < prices.length; i += 1) {
  if (prices[i] > 20) {
    break;
  }
  console.log(prices[i]);
}

The loop logs 10, then encounters 20? No: with the condition shown, it logs 10 and 20, then stops before logging 30 because 30 is greater than 20. The important property is that no later iteration runs after break.

continue skips the current iteration

for (let i = 0; i < prices.length; i += 1) {
  if (prices[i] <= 10) {
    continue;
  }
  console.log(prices[i]);
}

Here the update expression still runs, and the next index is tested. This makes continue useful for excluding known cases while retaining the rest of the loop.

When the index is part of the algorithm

Some algorithms need two positions or need to move the index conditionally:

const values = [3, 8, 2, 9];
let firstPairOverTen = null;

for (let i = 0; i < values.length - 1; i += 1) {
  if (values[i] + values[i + 1] > 10) {
    firstPairOverTen = [i, i + 1];
    break;
  }
}

You can express many such tasks with array methods, but a loop makes the index movement and early exit explicit.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Return values: the distinction that prevents bugs

Construct Callback result Method result Typical use
map() Becomes an element in the new array New array with the same length as the visited array shape Transform each input
forEach() Discarded undefined Perform an action
for There is no callback No method result; use variables, return, or break in surrounding code Custom control flow

A common mistake is expecting this function to return a transformed array:

function double(values) {
  values.forEach((value) => value * 2);
}

console.log(double([1, 2, 3])); // undefined

Use map() when the function’s contract is “return one doubled value for each input”:

function double(values) {
  return values.map((value) => value * 2);
}

Alternatively, use an explicit accumulator when the output is not one-to-one:

function sum(values) {
  let total = 0;
  for (let i = 0; i < values.length; i += 1) {
    total += values[i];
  }
  return total;
}

Early exit: why forEach() cannot be broken normally

break and continue belong to loop statements. They cannot be used to leave a forEach() callback in the same way:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// Syntax error: break is not inside a loop statement.
values.forEach((value) => {
  if (value > 20) break;
});

Do not throw an exception merely to simulate normal loop control. Choose an operation whose contract includes short-circuiting.

Use some() for “does any element match?”

const hasLargeValue = values.some((value) => value > 20);

some() stops when its predicate succeeds and returns a boolean.

Use find() for the matching element

const firstLargeValue = values.find((value) => value > 20);

find() stops at the first match and returns that element, or undefined when no element matches.

Use findIndex() for the matching position

const firstLargeIndex = values.findIndex((value) => value > 20);

findIndex() returns the first matching index or -1. If you need several conditions, cleanup, or multiple statements around the exit, use a for loop.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Asynchronous callbacks: the forEach() trap

An async callback does not make forEach() wait. The method invokes the callbacks and returns undefined without awaiting the promises they create.

async function saveAll(records) {
  records.forEach(async (record) => {
    await save(record);
  });
  console.log('done');
}

In this example, “done” can be logged before any save has finished. Rejections also are not collected by forEach() for the caller to await. The problem is not that the callback cannot be asynchronous; it is that forEach() has no promise-aggregation contract.

Run asynchronous work sequentially

Use an await-capable loop when each operation must finish before the next starts, or when order and rate limits matter:

async function saveInOrder(records) {
  for (const record of records) {
    await save(record);
  }
}

A classic index loop works when you need numeric index control:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
async function saveInOrder(records) {
  for (let i = 0; i < records.length; i += 1) {
    await save(records[i]);
  }
}

These functions resolve after every awaited operation completes. They also make it straightforward to stop on an error or add a conditional continue.

Run asynchronous work concurrently

If operations are independent and concurrent execution is appropriate, create the promises and await their aggregate:

async function saveConcurrently(records) {
  await Promise.all(records.map((record) => save(record)));
}

Here map() is useful because its result is the array of promises consumed by Promise.all(). Do not use this pattern when the server, database, API quota, ordering requirement, or resource limit requires sequential work. If one promise rejects, Promise.all() rejects; design error handling for that behavior.

Sparse arrays and empty slots

map() and forEach() do not call their callbacks for empty slots in a sparse array. An empty slot is different from an element whose value is explicitly undefined.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const sparse = [];
sparse[2] = 'third';

sparse.forEach((value, index) => {
  console.log(index, value);
});
// Logs only: 2 'third'

const mapped = sparse.map((value) => value.toUpperCase());
console.log(mapped.length); // 3
// The result retains an empty slot at indexes 0 and 1.

A classic indexed for loop evaluates every numeric index you request, so reading a missing position produces undefined. If your algorithm must distinguish holes, test with care rather than assuming all three constructs visit the same set of positions.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common scenarios and the right choice

Convert API records into display rows

const rows = records.map((record) => ({
  id: record.id,
  title: record.name.trim()
}));

Each input record produces one output row, so map() documents the one-to-one transformation.

Write each item to a logger

records.forEach((record) => {
  logger.info('record_seen', { id: record.id });
});

No collection is needed, so forEach() is clearer than creating and discarding an array.

Stop scanning after a threshold

for (let i = 0; i < readings.length; i += 1) {
  if (readings[i] > LIMIT) {
    alert(readings[i]);
    break;
  }
}

A loop expresses the required exit directly. If you only need to know whether a threshold exists, readings.some((reading) => reading > LIMIT) is more declarative.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Skip invalid records and process the rest

for (const record of records) {
  if (!record.id) {
    continue;
  }
  process(record);
}

continue keeps the valid-record path uncluttered. A filter() followed by forEach() can also work when creating an intermediate collection is acceptable.

Build request promises, then await all of them

const requests = endpoints.map((endpoint) => fetch(endpoint));
const responses = await Promise.all(requests);

The array returned by map() is used, so this is a meaningful transformation rather than a side-effect loop.

Is one faster: for, forEach(), or map()?

There is no universal speed winner established by the cited documentation. Runtime depends on the JavaScript engine, version, array shape, callback work, allocation, input size, and surrounding program. A statement such as “for is always fastest” is too broad without a benchmark that specifies those conditions.

Start with the clearest correct contract. If performance is genuinely important, measure representative code in the target engine and workload:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Use the same input sizes and data shapes your application sees.
  • Include the real callback work, not an empty operation that exaggerates loop overhead.
  • Warm up the code when the engine’s optimization behavior affects the measurement.
  • Measure enough iterations to reduce noise, and compare the complete operation, including allocations.
  • Confirm that an apparent improvement does not change behavior, ordering, error handling, or memory use.

Optimize after profiling identifies a real bottleneck. Choosing map() for a transformation and forEach() for a side effect usually improves maintainability regardless of tiny loop-overhead differences.

Failure modes and fixes

  • “My forEach() result is undefined.” The method always returns undefined. Replace it with map() for an output array or return an accumulator from a surrounding function.
  • “My map() code does not change anything.” You may be ignoring the returned array. Assign, return, or consume it.
  • “break will not compile inside forEach().” Use a for loop, or use some(), find(), or findIndex() for their built-in short-circuit behavior.
  • “The function logs completion before async work finishes.” An async forEach() callback is not awaited. Use a sequential await-capable loop or Promise.all(items.map(work)) when concurrency is safe.
  • “A sparse array produced fewer callbacks than expected.” Empty slots are skipped by map() and forEach(). Decide whether holes should be preserved, filled, or handled by an indexed loop.
  • “The code is hard to read despite using a short method.” Switch to the construct that states the algorithm’s real contract. Brevity is not a substitute for explicit control flow.

A practical selection checklist

  1. Ask whether the operation must produce a new array. If yes, use map() and use its return value.
  2. If there is no result collection, ask whether the callback is simply performing an action. If yes, use forEach().
  3. If you need break, continue, a mutable index, multiple counters, or conditional index movement, use a for loop.
  4. If every asynchronous operation must finish in sequence, put await in an await-capable loop.
  5. If independent asynchronous operations may run together, build promises with map() and explicitly await Promise.all() or another deliberate aggregation strategy.
  6. If the task ends at the first match, prefer some(), find(), or findIndex() unless the loop needs additional control.
  7. Check sparse-array behavior when inputs may contain empty slots.
  8. Benchmark only after correctness and clarity are established, and only in the environment that matters.

Further reading

For broader JavaScript coverage, O’Reilly lists JavaScript: The Definitive Guide, 7th Edition by David Flanagan, published in May 2020. Its coverage includes arrays, iterators, Promises, and async/await; it is general reference material rather than a dedicated comparison of these three loop forms.

Technical references

The behavior described here follows the current MDN Web Docs references for Array.prototype.forEach(), Array.prototype.map(), and the for statement, plus MDN’s indexed-collections guide, accessed October 7, 2026.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.