10 Step Function Patterns Every Serverless Developer Should Know

thrubit stepfunction patterns

AWS Step Functions simplify complex workflows by allowing developers to visually orchestrate serverless applications. Instead of chaining Lambdas with manual code, Step Functions use defined patterns to handle concurrency, retries, and branching logic in a clear, maintainable way. Below are ten essential Step Function patterns every serverless developer should master.


1. Sequential Pattern

01 sequential

The simplest pattern is a linear sequence of tasks where each state runs after the previous one completes. This is ideal for predictable, ordered processes such as ETL pipelines or API request chains.

Use Case: Validate → Process → Store → Notify

What it is

A straight line of states that must occur in order.

When to use

Data pipelines, provisioning flows, multi API workflows where each step depends on the previous step.

Workflow Library

Browse 60+ ready-to-run Step Functions workflows

Real-world ASL templates for AI, finance, healthcare, gaming, and more — run locally with Thrubit.

Explore workflows

ASL

{
  "StartAt": "Validate",
  "States": {
    "Validate": { "Type": "Task", "Resource": "arn:aws:lambda:...:validate", "Next": "Transform" },
    "Transform": { "Type": "Task", "Resource": "arn:aws:lambda:...:transform", "Next": "Persist" },
    "Persist": { "Type": "Task", "Resource": "arn:aws:lambda:...:persist", "End": true }
  }
}

Tips

  • Push data shape normalization to the earliest step so later states are simpler.
  • Fail fast on validation to save cost.
  • Use ResultPath to keep payloads small between steps.

2. Parallel Pattern

02 parallel

The Parallel state allows multiple branches to execute simultaneously. This pattern maximizes efficiency by running independent tasks at the same time.

Use Case: Process multiple files, run analytics jobs, or handle multi-region deployments concurrently.

What it is

Run independent branches at the same time and join when all complete or a branch fails.

When to use

Fan out independent enrichment tasks, multi region checks, writing to several backends.

ASL

{
  "StartAt": "InParallel",
  "States": {
    "InParallel": {
      "Type": "Parallel",
      "Branches": [
        { "StartAt": "A", "States": { "A": { "Type": "Task", "Resource": "arn:aws:lambda:...:a", "End": true } } },
        { "StartAt": "B", "States": { "B": { "Type": "Task", "Resource": "arn:aws:lambda:...:b", "End": true } } }
      ],
      "Next": "Join"
    },
    "Join": { "Type": "Succeed" }
  }
}

Tips

  • Keep branch outputs small. Use ResultSelector to return only required fields.
  • Prefer service integrations for I O bound work to avoid Lambda concurrency spikes.
  • Add a Catch on the Parallel state to centralize branch failures.

3. Map Pattern

03 map

The Map state dynamically iterates over a list of items and applies the same logic to each item, either sequentially or in parallel. It scales naturally for workloads that need to handle arrays of data.

Use Case: Batch processing, image transformations, or sending notifications to a list of users.

What it is

Iterate a substate machine over each element of an array. Supports distributed concurrency and batching.

When to use

Process N files, N records, or N API calls with the same logic.

ASL

{
  "StartAt": "ProcessItems",
  "States": {
    "ProcessItems": {
      "Type": "Map",
      "ItemsPath": "$.items",
      "MaxConcurrency": 20,
      "Parameters": {
        "item.$": "$$.Map.Item.Value",
        "requestId.$": "$.requestId"
      },
      "Iterator": {
        "StartAt": "Work",
        "States": {
          "Work": { "Type": "Task", "Resource": "arn:aws:lambda:...:work", "End": true }
        }
      },
      "End": true
    }
  }
}

Tips

  • Use MaxConcurrency to protect downstream APIs.
  • Use ItemSelector or Parameters to trim each subpayload.
  • If items are very large, store in S3 and pass object keys rather than inlining data.

4. Choice Pattern

Choice states introduce conditional logic similar to “if-else” statements. This enables branching paths based on input data, ensuring workflows adapt to real-time conditions.

Start free. No AWS account needed.
ZERO AWS costs.

Download Thrubit and run your first state machine locally in under five minutes. No cloud setup, no IAM policies, no waiting.

Use Case: Route requests based on user type, data format, or error type.

What it is

Data driven branching. Behaves like if or switch.

When to use

Route based on flags, schemas, ranges, or presence of fields.

ASL

{
  "StartAt": "Route",
  "States": {
    "Route": {
      "Type": "Choice",
      "Choices": [
        { "Variable": "$.type", "StringEquals": "premium", "Next": "PremiumPath" },
        { "Variable": "$.amount", "NumericGreaterThanEquals": 1000, "Next": "HighValuePath" }
      ],
      "Default": "StandardPath"
    },
    "PremiumPath": { "Type": "Task", "Resource": "arn:aws:lambda:...:premium", "End": true },
    "HighValuePath": { "Type": "Task", "Resource": "arn:aws:lambda:...:high", "End": true },
    "StandardPath": { "Type": "Task", "Resource": "arn:aws:lambda:...:std", "End": true }
  }
}
A Choice state labeled IsPremium routes to Apply Premium on true or to Standard Flow on false, both ending the workflow.

Tips

  • Always define a Default path for unexpected inputs.
  • Validate Choice variables exist to avoid silent misroutes.
  • Keep predicate logic simple and test with representative payloads.

5. Wait Pattern

05 wait

The Wait state pauses execution for a set time or until a specific timestamp. It’s essential for delaying retries, rate-limiting calls, or waiting for external systems to update.

Use Case: Wait for data synchronization or time-based triggers.

What it is

Pause for a duration or until a timestamp.

When to use

Backoff, rate limiting, pause between polls, legal hold windows.

ASL

{
  "StartAt": "Backoff",
  "States": {
    "Backoff": { "Type": "Wait", "SecondsPath": "$.sleepSeconds", "Next": "Check" },
    "Check": { "Type": "Task", "Resource": "arn:aws:lambda:...:check", "End": true }
  }
}

Tips

  • Use SecondsPath so callers control delays per request.
  • For long waits, prefer event driven callbacks rather than polling loops.

6. Retry and Catch Pattern

06 retry catch

Retries automatically handle transient failures without breaking the workflow. Catch blocks manage exceptions and redirect execution to recovery states.

Use Case: Automatically retry failed API calls, then log or alert after repeated failures.

What it is

Automatic retries with backoff and circuit breaking. Catch to route failures.

When to use

Transient errors, throttling, flaky upstreams, partial fallbacks.

ASL

{
  "StartAt": "CallAPI",
  "States": {
    "CallAPI": {
      "Type": "Task",
      "Resource": "arn:aws:states:::aws-sdk:dynamodb:putItem",
      "Parameters": { "TableName": "T", "Item.$": "$.item" },
      "Retry": [
        { "ErrorEquals": ["States.Timeout","States.TaskFailed"], "IntervalSeconds": 2, "BackoffRate": 2.0, "MaxAttempts": 4 }
      ],
      "Catch": [
        { "ErrorEquals": ["States.ALL"], "ResultPath": "$.error", "Next": "OnError" }
      ],
      "Next": "Done"
    },
    "OnError": { "Type": "Task", "Resource": "arn:aws:lambda:...:notify", "End": true },
    "Done": { "Type": "Succeed" }
  }
}

Tips

  • Match retry policies to error classes you actually see.
  • Cap MaxAttempts to avoid runaway cost.
  • Always route to a compensating or notification path on final failure.

7. Pass Pattern

The Pass state passes input directly to output without running a task. It’s commonly used for data transformations or placeholder logic during testing.

Use Case: Simplify JSON structures or temporarily stub out unimplemented functions.

What it is

No operation. Optionally transforms data.

When to use

Prototyping, reshaping payloads, injecting constants, splitting a big JSON into smaller parts.

ASL

{
  "StartAt": "Shape",
  "States": {
    "Shape": {
      "Type": "Pass",
      "Parameters": { "id.$": "$.request.id", "tenant.$": "$.context.tenant" },
      "ResultPath": "$.shaped",
      "Next": "NextTask"
    },
    "NextTask": { "Type": "Task", "Resource": "arn:aws:lambda:...:next", "End": true }
  }
}

Tips

  • Use ResultPath: null to drop large intermediate data.
  • Use Parameters rather than embedding large literal JSON.

8. Succeed and Fail Patterns

08 succeed fail

The Succeed state ends execution successfully, while the Fail state terminates the workflow due to an error. Both provide clear outcomes for downstream systems.

Use Case: Mark a process complete or trigger alerts when failure conditions occur.

What it is

Explicit terminal states with success or error.

When to use

Short circuit completion or force a hard stop with a specific error.

ASL

{
  "StartAt": "Guard",
  "States": {
    "Guard": {
      "Type": "Choice",
      "Choices": [{ "Variable": "$.enabled", "BooleanEquals": true, "Next": "Run" }],
      "Default": "Stop"
    },
    "Run": { "Type": "Task", "Resource": "arn:aws:lambda:...:run", "Next": "Done" },
    "Done": { "Type": "Succeed" },
    "Stop": { "Type": "Fail", "Error": "FeatureDisabled", "Cause": "Flag is off" }
  }
}

Tips

  • Emit meaningful Error and Cause strings to simplify alarms and dashboards.
  • Prefer Succeed over ending a Pass state for clarity.

9. Nested Workflows Pattern

09 nested

Nested workflows allow one Step Function to call another, making large systems modular and maintainable. Each subworkflow can be updated independently.

Use Case: A master orchestration that invokes multiple specialized workflows for billing, reporting, or auditing.

What it is

A parent workflow invokes a child workflow as a task. Promotes modularity and reuse.

When to use

Shared subflows like billing, audit trails, document processing.

ASL

{
  "StartAt": "InvokeChild",
  "States": {
    "InvokeChild": {
      "Type": "Task",
      "Resource": "arn:aws:states:::states:startExecution.sync",
      "Parameters": {
        "StateMachineArn": "arn:aws:states:...:stateMachine:Child",
        "Input.$": "$"
      },
      "Next": "WrapUp"
    },
    "WrapUp": { "Type": "Task", "Resource": "arn:aws:lambda:...:wrap", "End": true }
  }
}

Tips

  • Use .sync to wait for child completion and capture its output.
  • Version child machines and keep contracts stable across teams.
  • Enforce input and output schemas at boundaries.

10. Dynamic Parallelism Pattern

Dynamic Parallelism combines Map and Parallel concepts, letting workflows adapt the number of branches at runtime. This pattern handles scaling workloads with varying input sizes.

Use Case: Automatically fan out to process large datasets or multi-user operations dynamically.

What it is

Combine Map with inner Parallel or combine Choice produced arrays with Map to scale branches at runtime.

When to use

Variable sized fan out where each item itself requires multiple substeps.

ASL

{
  "StartAt": "MapUsers",
  "States": {
    "MapUsers": {
      "Type": "Map",
      "ItemsPath": "$.users",
      "MaxConcurrency": 10,
      "Iterator": {
        "StartAt": "UserParallel",
        "States": {
          "UserParallel": {
            "Type": "Parallel",
            "Branches": [
              { "StartAt": "Notify", "States": { "Notify": { "Type": "Task", "Resource": "arn:aws:lambda:...:notify", "End": true } } },
              { "StartAt": "Index", "States": { "Index": { "Type": "Task", "Resource": "arn:aws:lambda:...:index", "End": true } } }
            ],
            "End": true
          }
        }
      },
      "End": true
    }
  }
}
Three parallel branches Fetch Profile, Fetch Orders, and Fetch Recs converge to Join Aggregate, then End.

Tips

  • Consider memory growth in the Map output. Persist large per item results to S3 then merge keys at the end.
  • If any sub branch can fail independently, add Catch inside the iterator to continue for other items.

Final Thoughts

Mastering these Step Function patterns enables developers to create scalable, fault-tolerant, and maintainable serverless systems. Whether you are automating batch jobs, building resilient APIs, or orchestrating multi-step data pipelines, these patterns form the foundation of robust AWS Step Function design.

Cross cutting best practices

Payload hygiene
Use InputPath, Parameters, ResultSelector, and ResultPath to strictly control payload size. Pass handles or S3 keys, not blobs.

Service integrations first
Prefer the arn:aws:states::: integrations for DynamoDB, SNS, SQS, EventBridge, SageMaker, Bedrock, and others. This reduces Lambda code and cold starts.

Idempotency
Put idempotency keys in payloads and enforce at the edges. Retries will otherwise create duplicates.

Observability

  • Enable X Ray on the state machine and Lambdas.
  • Emit structured logs with correlation ids.
  • Add metrics filters for ExecutionFailed and branch error counts.
  • Tag executions with business ids for searchability.

Testing

  • Unit test state input output contracts using mock executions.
  • Create small golden payloads per Choice path and per Map item shape.
  • Load test with realistic concurrency on Map and Parallel.

Cost control

  • Prefer Wait over sleeping in Lambdas.
  • Bound retries and concurrency.
  • Use Express Workflows for very high volume short lived flows and Standard for longer running or audit heavy flows.

Security

  • Scope IAM to per state action level where possible.
  • Do not pass secrets in state payloads. Use Secrets Manager or Parameter Store and resolve inside Task states.
Free Trial