# `step.run`

`step.run()` makes one operation a durable step that Inngest can retry if it fails. Inngest saves a completed step's result, so later retries can continue without repeating that work.

They are useful when:

- You call an external API or write to a database.
- Later steps need the result of earlier work.
- You want to retry one failed operation without repeating completed steps.

## Run two steps

This function loads a task, then processes it. The client import assumes you have created an Inngest client in `./client`. Replace `loadTask` and `processTaskRecord` with your application's functions.

```typescript {{ title: "TypeScript" }}
import { inngest } from "./client";

export const processTask = inngest.createFunction(
  { id: "process-task", triggers: { event: "app/task.created" } },
  async ({ event, step }) => {
    const task = await step.run("load-task", () =>
      loadTask(event.data.taskId)
    );

    return step.run("process-task", () => processTaskRecord(task));
  }
);
```

```python {{ title: "Python" }}
import inngest

from .client import inngest_client

@inngest_client.create_function(
    fn_id="process-task",
    trigger=inngest.TriggerEvent(event="app/task.created"),
)
async def process_task(ctx: inngest.Context) -> dict[str, bool]:
    task = await ctx.step.run(
        "load-task", load_task, ctx.event.data["taskId"]
    )

    return await ctx.step.run("process-task", process_task_record, task)
```

```go {{ title: "Go" }}
import (
	"context"

	"github.com/inngest/inngestgo"
	"github.com/inngest/inngestgo/step"
)

type TaskCreated struct {
	TaskID string `json:"taskId"`
}

func ProcessTask(client inngestgo.Client) (inngestgo.ServableFunction, error) {
	return inngestgo.CreateFunction(
		client,
		inngestgo.FunctionOpts{ID: "process-task"},
		inngestgo.EventTrigger("app/task.created", nil),
		func(ctx context.Context, input inngestgo.Input[TaskCreated]) (any, error) {
			task, err := step.Run(ctx, "load-task", func(ctx context.Context) (Task, error) {
				return loadTask(ctx, input.Event.Data.TaskID)
			})
			if err != nil {
				return nil, err
			}

			return step.Run(ctx, "process-task", func(ctx context.Context) (Result, error) {
				return processTaskRecord(ctx, task)
			})
		},
	)
}
```

If `process-task` fails, Inngest retries that step. The completed `load-task` result stays saved, so the retry does not call `loadTask` again.

## Retries and side effects

If the handler throws, Inngest retries this step according to the function's `retries` setting. Each `step.run` call has its own retry count. By default, a step gets one initial attempt and up to four retries. A completed earlier step keeps its saved result.

Put database writes and API calls inside `step.run` so a resumed function does not repeat completed work. A failed attempt can still have changed an external system before it threw or timed out. Use an idempotency key, an upsert, or another duplicate-safe operation for effects that a retry could repeat. Use a non-retriable error for a permanent failure; catch an exhausted step failure when the workflow can recover or compensate.

## Results and limits

Inngest serializes returned values as JSON. Return only the data later work needs; for example, return a record ID instead of an entire record. Current platform limits are:

- 4 MiB step results
- 32 MiB total function run state
- 1,000 steps per function
- A step can run for up to two hours, subject to the hosting provider's shorter timeout

## Next

- [`step.run` reference](/docs-markdown/reference/typescript/v4/functions/step-run) for every option.
- [Primitives](/docs-markdown/durable-execution/primitives) for sleeps, waits, events, and invocations.
- [Error handling and retries](/docs-markdown/durable-execution/guides-and-advanced/error-handling) for failure and recovery patterns.
- [Handling idempotency](/docs-markdown/durable-execution/guides-and-advanced/idempotency) for duplicate-safe side effects.
- [Limits](/docs-markdown/durable-execution/limits) for step and run limits.