---
title: Batching & Parallel Processing
description: Process large collections in parallel batches with failure isolation between groups.
type: guide
summary: Split items into fixed-size batches, process each batch concurrently with Promise.allSettled, and pace batches with sleep to avoid overloading downstream services.
---

Use batching when you need to process a large list of items in parallel while controlling concurrency. Items are split into fixed-size batches, each batch runs concurrently, and failures in one batch don't affect others.

## When to use this

- Bulk data imports (contacts, orders, products from a CSV)
- Processing hundreds or thousands of items against external APIs
- Calling rate-limited APIs where you need to control concurrency
- Any fan-out where you want failure isolation between groups

## How it works

1. Records are split into fixed-size batches.
2. Each batch runs in parallel via `Promise.allSettled` — failures in one record don't affect others.
3. A `sleep()` between batches paces requests to avoid overloading downstream services.
4. After all batches, a summary is returned with succeeded/failed counts.

## Pattern

The workflow splits records into chunks, processes each chunk concurrently, tracks results per batch, and returns a final tally.

```typescript
import { sleep } from "workflow";

type Record = { name: string; email: string; role: string };

declare function processRecord(record: Record): Promise<string>; // @setup

export async function batchImport(records: Record[], batchSize: number) {
  "use workflow";

  let totalSucceeded = 0;
  let totalFailed = 0;

  for (let i = 0; i < records.length; i += batchSize) {
    const batch = records.slice(i, i + batchSize);

    // Run batch in parallel — failures are isolated per record
    const outcomes = await Promise.allSettled( // [!code highlight]
      batch.map((record) => processRecord(record))
    );

    for (let j = 0; j < outcomes.length; j++) {
      if (outcomes[j].status === "fulfilled") {
        totalSucceeded++;
      } else {
        totalFailed++;
      }
    }

    // Pace between batches to avoid overloading downstream
    if (i + batchSize < records.length) {
      await sleep("1s"); // [!code highlight]
    }
  }

  return { total: records.length, succeeded: totalSucceeded, failed: totalFailed };
}
```

### Step function

Each record is processed in its own step with full Node.js access and automatic retries.

```typescript
type Record = { name: string; email: string; role: string };

async function processRecord(record: Record): Promise<string> {
  "use step";
  const res = await fetch(`https://api.example.com/contacts`, {
    method: "POST",
    body: JSON.stringify(record),
  });
  if (!res.ok) throw new Error(`Failed to import ${record.email}`);
  const { id } = await res.json();
  return id;
}
```

## Adapting to your use case

- Replace the `Record` type with your actual data shape (orders, images, products, etc.).
- Replace `processRecord()` with your real import logic — DB upserts, API calls, file processing.
- Tune `batchSize` and the `sleep()` duration to match your downstream rate limits.
- Add or remove tracking as needed — the pattern works with any item type.

## Tips

- **Use `Promise.allSettled` over `Promise.all`** when you want to continue even if some items fail. `Promise.all` rejects on the first failure; `allSettled` waits for everything and tells you what failed.
- **Tune batch size to your downstream API limits.** If the API allows 10 concurrent requests, use `batchSize: 10`.
- **Add pacing with `sleep()`** between batches to respect rate limits. The sleep is durable — it survives cold starts.
- **Each `processRecord` call is an independent step.** If one fails, it retries up to 3 times without affecting other items in the batch.

## Key APIs

- [`"use workflow"`](/docs/foundations/workflows-and-steps) -- marks the orchestrator function
- [`"use step"`](/docs/foundations/workflows-and-steps) -- marks functions that run with full Node.js access
- [`sleep()`](/docs/api-reference/workflow/sleep) -- pacing delay between batches
- [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) -- runs items in parallel, isolating failures
