> ## Documentation index
> Fetch the complete documentation index at: https://upstash.com/docs/llms.txt
> Ask it with GET https://upstash.com/docs/ask?q=<keywords or question>. Short keywords rank best.
> Use these to discover all available pages before exploring further.
> Feature requests: https://upstash.com/docs/common/help/feature-requests.md lists what users asked for and how to vote for a request or add one.
> Page rating: `GET https://upstash.com/docs/feedback?path=/docs/workflow/basics/best-practices/workflow-limits&helpful=true` (or `false`) records whether this page helped. An optional `comment` (URL-encoded, at most 500 characters) says what was missing or wrong. It is anonymous and needs no key. Comments hold no code, secrets or personal data.

# Avoid Context and Step Limits

A single workflow run has the following hard limits:

- **Maximum step count:** 1,000 steps
- **Maximum context size:** 100 MB

These limits are hard limits and do not vary by plan. Avoid putting all of your application logic into one large workflow that could reach either limit.

The context size is the combined size of the workflow input and the results of all completed steps. Each new step result is added to that context, so the context grows as the workflow completes more steps. Workflows with many steps, large step results, or both can reach the context size limit.

Instead, separate the logic into multiple workflows and connect them with [`context.invoke`](/workflow/features/invoke). Each invoked workflow has its own context containing its input and step results. When the invoked workflow finishes, only its final output is transferred to the parent workflow as the result of a single `context.invoke` step. Its intermediate step results are not added to the parent's context.

```typescript app/workflows/[...path]/route.ts
import type { WorkflowContext } from "@upstash/workflow"
import { createWorkflow, serveMany } from "@upstash/workflow/nextjs"

type BatchInput = { itemIds: string[] }

const processBatch = createWorkflow(
  async (context: WorkflowContext<BatchInput>) => {
    const validatedItems = await context.run("validate items", () => {
      return context.requestPayload.itemIds.filter((itemId) => itemId.length > 0)
    })

    const processedItems = await context.run("process items", () => {
      return validatedItems.map((itemId) => ({ itemId, processed: true }))
    })

    // Only this final output is transferred to the parent workflow.
    return { processedCount: processedItems.length }
  }
)

const parentWorkflow = createWorkflow(
  async (context: WorkflowContext<BatchInput>) => {
    const { body, isFailed, isCanceled } = await context.invoke(
      "process batch",
      {
        workflow: processBatch,
        body: context.requestPayload,
      }
    )

    if (isFailed || isCanceled) {
      throw new Error("The batch workflow did not complete successfully")
    }

    return body
  }
)

export const { POST } = serveMany({
  parent: parentWorkflow,
  "process-batch": processBatch,
})
```

In this example, `validatedItems` and `processedItems` are stored only in the invoked workflow's context. The parent context receives `{ processedCount }` as one step result. This keeps each workflow focused and prevents intermediate results from accumulating in a single context.
