> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nelkap.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Loops and parallel steps

> Run steps in loops and side by side, and split big jobs across several runs.

Steps are ordinary function calls, so they work in loops, in conditions, and in `Promise.all`. This page shows how Nelkap finds each one again, and how to handle jobs too big for a single run.

## Loops

Each step in a loop is saved on its own. An import that stops halfway goes on from the last batch that made it:

```ts theme={"theme":{"light":"light-plus","dark":"dark-plus"}}
export async function importContacts(fileId: string) {
  const batches = await nelkap.step.run('batches', () =>
    splitIntoBatches(fileId, 500),
  );

  for (const [index, batchId] of batches.entries()) {
    await nelkap.step.run(`batch ${index}`, () =>
      importRows(batchId),
    );
  }
}
```

The list of batches comes from a step too, so every call of the run loops over the same list.

A name can repeat: steps of the same kind and name are matched in the order your code reaches them. A loop of `step.run('send')` works, but a name that says which item it handles, like `batch 3`, is easier to read on the run's page.

## Parallel steps

Steps can run at the same time. Start them together and wait for all of them:

```ts theme={"theme":{"light":"light-plus","dark":"dark-plus"}}
const [profile, orders] = await Promise.all([
  nelkap.step.run('profile', () => getProfile(userId)),
  nelkap.step.run('orders', () => getOrders(userId)),
]);
```

The two functions run in parallel, and each result is saved as it finishes. They're saved in the order your code called them, so a run that picks up later finds them the same way.

## Conditions

A step can sit behind an `if`, as long as the condition takes the same path on every call. Base it on the payload or on the result of an earlier step, never on something that can change between calls, like the time:

```ts theme={"theme":{"light":"light-plus","dark":"dark-plus"}}
const user = await nelkap.step.run('user', () =>
  getUser(userId),
);

if (user.plan === 'pro') {
  await nelkap.step.run('pro welcome', () =>
    sendProWelcome(userId),
  );
}
```

## Step limit

A run holds up to 100 steps of every kind, and 32 MB of saved steps in all. A run that goes over fails with `stepLimitReached`. Two ways keep big jobs under the limit:

* Put several items in each step. A step that imports 500 rows counts once.
* Split the job across runs, as below.

## Fan-out

For a job of any size, give each part its own run. A first workflow lists the parts and starts a run for each one, then each run does its part:

```ts theme={"theme":{"light":"light-plus","dark":"dark-plus"}}
export async function importFile(fileId: string) {
  const batches = await nelkap.step.run('batches', () =>
    splitIntoBatches(fileId, 500),
  );

  for (const [index, batchId] of batches.entries()) {
    await nelkap.step.run(`start batch ${index}`, () =>
      nelkap.run(importBatch, batchId),
    );
  }
}

export async function importBatch(batchId: string) {
  await nelkap.step.run('import', () =>
    importRows(batchId),
  );
}
```

Starting each run in a step means a run that picks up later doesn't start the same batch twice. Give `importBatch` a [concurrency limit](https://docs.nelkap.com/guides/concurrency.md) to control how many batches go at once.

The first workflow still counts one step per batch. For more batches than one run can start, add a level: the first workflow starts a run per chunk of batches, and each of those starts a run per batch.

The first run ends once it has started every batch, without waiting for them. To act when the last batch is done, count the batches done in your database, as the [large import example](https://docs.nelkap.com/examples/large-import.md) does.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.