JavaScript
array manipulation
asynchronous programming
sequence processing
function execution

execute a function against array items in sequence

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

To execute a function against array items in sequence (where each operation completes before the next starts), use for...of with await in JavaScript. Unlike Promise.all (which runs concurrently) or .forEach (which does not await async callbacks), a for...of loop processes one item at a time. For synchronous operations, reduce, forEach, or plain for loops all work. For async operations, only for...of with await or reduce with await guarantee sequential execution.

Synchronous Sequential Execution

for...of Loop

javascript
1const items = [1, 2, 3, 4, 5];
2
3for (const item of items) {
4  console.log(item * 2);
5}
6// 2, 4, 6, 8, 10 — each processed in order

forEach

javascript
1const items = ['a', 'b', 'c'];
2
3items.forEach((item, index) => {
4  console.log(`${index}: ${item}`);
5});
6// 0: a
7// 1: b
8// 2: c

reduce (Accumulating Results)

javascript
1const numbers = [1, 2, 3, 4, 5];
2
3const sum = numbers.reduce((acc, num) => acc + num, 0);
4console.log(sum);  // 15
5
6// Build a string sequentially
7const csv = numbers.reduce((acc, num, i) => {
8  return i === 0 ? String(num) : `${acc},${num}`;
9}, '');
10console.log(csv);  // "1,2,3,4,5"

Async Sequential Execution

javascript
1async function processSequentially(urls) {
2  const results = [];
3
4  for (const url of urls) {
5    const response = await fetch(url);        // Wait for this request
6    const data = await response.json();       // Before starting the next
7    results.push(data);
8    console.log(`Fetched: ${url}`);
9  }
10
11  return results;
12}
13
14// Each URL is fetched one at a time, in order
15await processSequentially([
16  'https://api.example.com/users/1',
17  'https://api.example.com/users/2',
18  'https://api.example.com/users/3'
19]);

Each await pauses the loop until the async operation completes, then moves to the next item.

reduce with await

javascript
1async function processSequentially(items) {
2  return items.reduce(async (prevPromise, item) => {
3    const results = await prevPromise;  // Wait for previous item
4    const result = await processItem(item);
5    return [...results, result];
6  }, Promise.resolve([]));
7}
8
9// Usage
10const results = await processSequentially([1, 2, 3]);

Each iteration awaits the previous Promise before starting. The accumulator is a Promise chain that builds up the results array.

for Loop (Index-Based)

javascript
1async function uploadFiles(files) {
2  for (let i = 0; i < files.length; i++) {
3    console.log(`Uploading ${i + 1}/${files.length}: ${files[i].name}`);
4    await uploadFile(files[i]);
5    console.log(`Done: ${files[i].name}`);
6  }
7}

Use a traditional for loop when you need the index for progress reporting or to reference adjacent elements.

Why forEach Does NOT Work for Async

javascript
1// THIS DOES NOT WORK — all requests fire simultaneously
2async function broken(urls) {
3  urls.forEach(async (url) => {
4    const response = await fetch(url);  // await is inside the callback
5    console.log(`Fetched: ${url}`);
6  });
7  console.log('Done');  // Prints BEFORE any fetch completes!
8}

forEach ignores the return value of its callback. Since an async callback returns a Promise, forEach fires all callbacks immediately without waiting. The function exits before any fetch completes.

javascript
1// CORRECT — for...of respects await
2async function correct(urls) {
3  for (const url of urls) {
4    const response = await fetch(url);
5    console.log(`Fetched: ${url}`);
6  }
7  console.log('Done');  // Prints AFTER all fetches complete
8}

Sequential vs Concurrent vs Batched

javascript
1// SEQUENTIAL — one at a time (slowest, safest for rate limits)
2async function sequential(items) {
3  for (const item of items) {
4    await process(item);
5  }
6}
7
8// CONCURRENT — all at once (fastest, may overwhelm server)
9async function concurrent(items) {
10  await Promise.all(items.map(item => process(item)));
11}
12
13// BATCHED — N at a time (balanced)
14async function batched(items, batchSize = 3) {
15  for (let i = 0; i < items.length; i += batchSize) {
16    const batch = items.slice(i, i + batchSize);
17    await Promise.all(batch.map(item => process(item)));
18  }
19}

Use sequential execution when:

  • Each operation depends on the previous result
  • You need to respect API rate limits
  • Order of side effects matters (e.g., database writes)

Pipeline Pattern

javascript
1// Apply a series of transformations in sequence
2const pipeline = [
3  (x) => x * 2,
4  (x) => x + 10,
5  (x) => x.toString(),
6  (s) => `Result: ${s}`
7];
8
9const result = pipeline.reduce((value, fn) => fn(value), 5);
10console.log(result);  // "Result: 20"
11
12// Async pipeline
13const asyncPipeline = [
14  async (x) => await fetchMultiplier(x),
15  async (x) => await applyDiscount(x),
16  async (x) => await formatCurrency(x)
17];
18
19const finalResult = await asyncPipeline.reduce(
20  async (prevPromise, fn) => fn(await prevPromise),
21  Promise.resolve(100)
22);

Other Languages

Python

python
1import asyncio
2
3# Synchronous
4items = [1, 2, 3, 4, 5]
5results = []
6for item in items:
7    results.append(item * 2)
8
9# Async sequential
10async def process_sequentially(items):
11    results = []
12    for item in items:
13        result = await process_item(item)
14        results.append(result)
15    return results

C#

csharp
1// Async sequential with foreach
2var items = new[] { "url1", "url2", "url3" };
3var results = new List<string>();
4
5foreach (var item in items)
6{
7    var result = await FetchAsync(item);
8    results.Add(result);
9}

Common Pitfalls

  • Using .map() for sequential async: items.map(async item => await process(item)) creates an array of Promises that all start immediately. Use for...of for true sequential execution, or Promise.all(items.map(...)) for intentional concurrency.
  • forEach with async callbacks: forEach does not await async callbacks. The enclosing function continues immediately. This is the most common mistake when trying to process items sequentially.
  • Forgetting error handling in loops: If one item fails in a for...of loop, the remaining items are skipped. Wrap the body in try/catch if you want to continue processing despite errors.
  • Reduce with async is hard to read: While reduce works for async sequential execution, it is harder to understand than for...of. Prefer for...of for async code and reserve reduce for synchronous accumulation.
  • Performance with large arrays: Sequential execution of N async operations takes the sum of all operation times. If operations are independent, consider concurrent (Promise.all) or batched execution for better throughput.

Summary

  • Use for...of with await for async sequential execution — it is the clearest pattern
  • forEach does not await async callbacks — it fires all callbacks immediately
  • Use reduce for synchronous accumulation or functional pipeline composition
  • Promise.all runs concurrently, not sequentially — use it when order does not matter
  • For rate-limited APIs, use sequential or batched processing
  • Wrap loop bodies in try/catch if individual failures should not stop processing

Course illustration
Course illustration

All Rights Reserved.