Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › Array Methods

Pick the Right JavaScript Array Loop for Results and Control

By Freedom251 Team · · 9 min read

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

Use map() when every visited element should become a value in a new array, forEach() when you need an action for each present element but no result collection, and a for loop when you need explicit indexes or control flow such as break and continue. These constructs have different contracts. Choosing among them is primarily a question of the result you need and whether the iteration must stop, wait, or run in a particular order.

The short decision guide

Start with the outcome rather than the syntax. Ask what the loop is supposed to produce, whether it may stop early, and whether each operation must be awaited before the next one starts.

As an Amazon Associate I earn from qualifying purchases.

What your code needs Best fit Reason
Create a transformed array map() Each callback result is placed in a new array.
Perform an action for every present element forEach() The callback is called for side effects; its return value is discarded and the method returns undefined.
Use an index, stop with break, or skip with continue for Loop statements expose those control-flow operations directly.
Await each asynchronous task in sequence for...of or another await-capable for loop An await in the loop body controls when the next iteration begins.
Stop when a condition is met some(), find(), or findIndex() These array methods short-circuit; forEach() does not.

If you choose map(), keep the returned array. If you choose forEach(), do not expect a returned collection. If you need to leave the iteration early, use a loop statement or an array method designed to short-circuit.

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

What each construct promises

map() is a transformation

map() calls a callback for each assigned array index and builds a new array from the values returned by that callback. The original array is not the output; the value returned by map() is.

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 callback can return a number, string, object, array, or any other value. The important contract is one callback result per visited element, collected into a new array. A common mistake is to call map() only for its callback side effect and then ignore the returned array. MDN describes that pattern as an anti-pattern: when no transformed array is needed, use forEach() or for...of instead.

forEach() is an action over elements

forEach() invokes a callback for each present element and always returns undefined. Returning a value from the callback does not add anything to a result array.

const prices = [10, 20, 30];
const returnValue = prices.forEach((price) => {
  console.log(`Price: ${price}`);
  return price * 1.1;
});

console.log(returnValue); // undefined

The returned value in the callback is discarded. Use this form when the useful result is an observable action such as logging, updating an existing object, sending a notification, or adding a DOM node. If the useful result is a new collection, map() communicates that intent more accurately.

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

A for loop is explicit control flow

A traditional for loop gives you an index and places the loop body under normal statement control. That makes it the direct choice when an iteration may end early, skip selected items, move by a custom increment, or coordinate several indexes.

const prices = [10, 20, 30];

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

This prints 10 and 20, then exits before processing 30. A continue statement skips the remainder of the current body and advances to the next iteration.

for (let i = 0; i < prices.length; i++) {
  if (prices[i] <= 10) {
    continue;
  }
  console.log(`Higher price: ${prices[i]}`);
}

When you do not need the numeric index, for...of can retain loop control while making the element value explicit:

for (const price of prices) {
  if (price > 20) break;
  console.log(price);
}

Return values: the difference that prevents many bugs

Construct What the callback returns What the construct returns When that return is useful
map() One value for each visited element A new array containing those values Use it as the transformed data for another operation or assignment.
forEach() Ignored undefined There is no collection result; the callback performs the work.
for Not applicable to the loop itself No automatic array result Assign, accumulate, return, or break explicitly in the body.

For example, this code does not populate doubled:

const doubled = [];
[1, 2, 3].forEach((number) => {
  number * 2;
});

console.log(doubled); // []

Use map() for the direct transformation:

const doubled = [1, 2, 3].map((number) => number * 2);

Or, if a side effect is intentional, make it visible in the loop body:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const doubled = [];
[1, 2, 3].forEach((number) => {
  doubled.push(number * 2);
});

The second version works, but map() states the one-to-one transformation more clearly and avoids managing an output array yourself.

Early exit: why forEach() cannot be broken

break and continue are statements for loop constructs. They cannot be used to leave a forEach() callback in the normal way. A return inside that callback returns from the callback invocation, not from the surrounding forEach(), so later elements are still visited.

const numbers = [1, 2, 3, 4];

numbers.forEach((number) => {
  if (number === 2) return;
  console.log(number);
});

// Logs 1, 3, and 4

If the operation must stop at a particular element, choose the construct that expresses that requirement.

Use a loop when the stopping logic is procedural

for (const number of numbers) {
  if (number === 2) break;
  console.log(number);
}

This stops immediately after processing 1. It also lets you combine several conditions, update external state, or use continue for selective processing.

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

Use a short-circuiting array method for a predicate

If the question is “does any element satisfy this condition?”, some() stops when the predicate succeeds. If you need the matching value, find() returns it; if you need its position, findIndex() returns the index.

const hasLargeOrder = orders.some((order) => order.total > 1000);
const firstLargeOrder = orders.find((order) => order.total > 1000);
const firstLargeIndex = orders.findIndex((order) => order.total > 1000);

These methods communicate a search or test better than trying to force an early exit from forEach().

Asynchronous callbacks: the forEach() trap

Marking a forEach() callback as async does not make forEach() wait for the promises it produces. The method invokes the callbacks and then returns undefined; there is no promise from forEach() for you to await.

await items.forEach(async (item) => {
  await saveItem(item);
});

console.log('This can run before saveItem finishes');

The outer await has nothing useful to wait for in this example. Depending on the surrounding code, errors from the asynchronous callbacks may also escape the expected error-handling path.

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

Sequential asynchronous work

Use an await-capable loop when each task must finish before the next starts, or when order and rate control matter.

async function saveInOrder(items) {
  for (const item of items) {
    await saveItem(item);
  }
}

The next iteration begins only after saveItem(item) fulfills. This is suitable when later work depends on earlier work, the remote service requires ordered requests, or you deliberately want to limit concurrency.

Concurrent asynchronous work

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

async function saveConcurrently(items) {
  await Promise.all(items.map((item) => saveItem(item)));
}

Here map() is being used to build the array of promises, and Promise.all() waits for that array. This is different from asking forEach() to handle promises: the promises are explicitly collected and awaited. Confirm that the service, browser, memory budget, and error policy can handle concurrent work before using this pattern.

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

Sparse arrays and empty slots

JavaScript arrays can have a length greater than their number of assigned elements. An empty slot is not the same thing as an element whose value is explicitly undefined. Both map() and forEach() skip empty slots: their callbacks run only for assigned indexes.

const sparse = [];
sparse[1] = 7;

let mapCalls = 0;
const mapped = sparse.map((value) => {
  mapCalls++;
  return value * 2;
});

let forEachCalls = 0;
sparse.forEach(() => {
  forEachCalls++;
});

console.log(sparse.length);  // 2
console.log(mapCalls);       // 1
console.log(forEachCalls);   // 1
console.log(0 in mapped);    // false: the hole remains a hole

If your algorithm must inspect every numeric position, including holes, an indexed for loop makes that behavior explicit. Reading an unassigned position produces undefined, so you can decide whether to treat it as missing data, skip it, or replace it.

for (let i = 0; i < sparse.length; i++) {
  const value = sparse[i];
  console.log(i, value); // logs index 0 with undefined and index 1 with 7
}

Choosing by intent: practical examples

Convert records to display labels

Each record produces one string, so map() is the natural contract.

const labels = users.map((user) => `${user.lastName}, ${user.firstName}`);

The resulting labels array can be passed to another function, rendered, filtered, or stored. No manual push() is needed.

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

Write audit messages

Logging does not require an output array. Use forEach() when every present record should trigger the action.

events.forEach((event) => {
  console.info(event.timestamp, event.type);
});

Stop processing after a threshold

A loop statement is clearer when processing should end as soon as a condition becomes true.

let total = 0;
for (const amount of payments) {
  total += amount;
  if (total >= target) break;
}

Skip invalid entries but continue

continue belongs naturally in a loop body.

for (const item of items) {
  if (!item.isValid) continue;
  process(item);
}

You can express some filtering-and-transformation pipelines with filter() followed by map(), but a loop may be easier to read when validation, logging, counters, and early exit all belong in one procedure.

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

Common mistakes and their fixes

  • Ignoring a map() result: If you call items.map(transform) without using the returned array, replace it with forEach(transform) or a for...of loop when the purpose is only a side effect.
  • Expecting forEach() to return transformed data: Assigning its result gives undefined. Use map() for a new array.
  • Trying to use break inside forEach(): Move the operation to a for or for...of loop, or choose some(), find(), or findIndex() for a short-circuiting test.
  • Awaiting forEach(): Replace it with a sequential await-capable loop or explicitly create promises and pass them to Promise.all() when concurrency is intended.
  • Assuming callbacks run for holes: Sparse-array holes are skipped by both map() and forEach(). Use an indexed loop if every position must be examined.
  • Choosing based on an assumed speed ranking: These constructs have different semantics, and there is no universal winner established by the referenced documentation. Benchmark representative code in the engine, runtime, and workload that matter to your application.

A repeatable selection process

  1. Define the output. If you need a new array with one derived value per visited element, start with map(). If there is no output collection, consider forEach() or a loop.
  2. Check for early termination. If the body needs break, continue, an index, or a custom increment, use a for loop. For a simple existence or search test, use a short-circuiting array method.
  3. Check asynchronous ordering. Sequential work belongs in an await-capable loop. Independent concurrent work can use Promise.all() around an explicitly constructed promise array.
  4. Check the data shape. If sparse arrays are possible, decide whether holes should be skipped or inspected as positions.
  5. Make the intent visible to the next reader. A transformation should look like a transformation; a side effect should look like a side effect; procedural control flow should use a loop statement.
  6. Measure only when it matters. If a hot path requires optimization, benchmark realistic inputs and operations in the target JavaScript engine instead of relying on a blanket claim that one form is faster.

Performance: what can and cannot be concluded

The referenced MDN documentation explains behavior and contracts, not a comparative benchmark that ranks for, forEach(), and map() across engines. Runtime performance varies with the JavaScript engine, version, array shape, callback work, allocation, and surrounding program. A loop that avoids creating an output array may have a different memory profile from map(), but that observation does not establish a universal speed result.

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.

For performance-sensitive code, measure the actual workload: use representative array sizes, realistic callback operations, warm-up behavior, and the deployment engine. Keep the clearer construct unless a measurement demonstrates a meaningful improvement and the change does not obscure correctness.

Further JavaScript reading

For a broad reference rather than a dedicated comparison of these three constructs, JavaScript: The Definitive Guide, 7th Edition by David Flanagan was published in May 2020. The publisher’s description includes arrays, iterators, Promises, and async/await, making it useful for readers who want the surrounding language concepts as well as array iteration.

Bottom line

Choose by contract: map() returns a transformed array, forEach() performs an action and returns undefined, and for gives you explicit control over indexes, stopping, skipping, and waiting. Once the desired result and control flow are clear, the appropriate construct is usually obvious.

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.

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.

Array MethodsAsync JavaScriptfor loopsforeachJavaScriptMap

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.