AWS Step Functions Basics
Introduction¶
Step Functions runs workflows defined as state machines in the Amazon States Language (ASL), a JSON DSL. It handles the parts of orchestration you'd otherwise hand-code: retries with backoff, error branching, parallel fan-out, timeouts, and a full visual execution history.
Standard vs Express¶
| Standard | Express | |
|---|---|---|
| Duration | up to 1 year | up to 5 minutes |
| Execution model | exactly-once, durable | at-least-once (sync) / at-most-once (async) |
| History | full, in the console, 90 days | to CloudWatch Logs only |
| Pricing | per state transition | per request + duration (much cheaper at high volume) |
| Use | long-running, human-in-the-loop, ETL, order fulfilment | high-volume event processing, short pipelines, API backends |
The states¶
{
"Comment": "Process an uploaded image",
"StartAt": "Validate",
"States": {
"Validate": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:validate",
"Retry": [{ "ErrorEquals": ["Lambda.TooManyRequestsException"], "IntervalSeconds": 2, "MaxAttempts": 5, "BackoffRate": 2.0 }],
"Catch": [{ "ErrorEquals": ["ValidationError"], "Next": "RejectUpload" }],
"Next": "ProcessSizes"
},
"ProcessSizes": {
"Type": "Map",
"ItemsPath": "$.sizes",
"MaxConcurrency": 4,
"Iterator": {
"StartAt": "Resize",
"States": {
"Resize": { "Type": "Task", "Resource": "arn:aws:lambda:...:function:resize", "End": true }
}
},
"Next": "Notify"
},
"Notify": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": { "TopicArn": "arn:aws:sns:...:uploads", "Message.$": "$.result" },
"End": true
},
"RejectUpload": { "Type": "Fail", "Error": "ValidationError", "Cause": "Bad upload" }
}
}
- Task — do work: invoke a Lambda, or call a service directly (SNS, SQS,
DynamoDB, ECS RunTask, Batch, Glue, another state machine, ~200 integrations).
.syncvariants wait for the job (ECS task, Glue job) to finish. - Choice — branch on the input (
"Variable": "$.status", "StringEquals": "APPROVED"). - Parallel — run several branches concurrently, wait for all.
- Map — run the same sub-workflow over each item of an array
(
ItemsPath), withMaxConcurrency. Distributed Map handles millions of items from S3. - Wait — pause (seconds, or until a timestamp).
- Pass / Succeed / Fail — shape data / terminal states.
Error handling¶
Retry— per error type, withIntervalSeconds,MaxAttempts,BackoffRate,MaxDelaySeconds,JitterStrategy. This replaces hand-rolled retry loops.Catch— route to a recovery state on an error, with the error passed in$.error.ErrorEquals:["States.ALL"]— catch-all.TimeoutSeconds/HeartbeatSecondson a Task — fail if it runs too long / stops sending heartbeats (forwaitForTaskTokenpatterns).
Human-in-the-loop / callbacks¶
The .waitForTaskToken pattern: a Task hands out a token, the workflow
pauses, and something external (a human clicking "approve", another system)
calls SendTaskSuccess/SendTaskFailure with the token to resume. Standard
workflows only (they can wait a year).
When a state machine beats glue code¶
- The workflow has branching, retries, parallelism, and long waits — all of which you'd otherwise build and test yourself.
- You want a visual audit trail of every execution (which step failed, with what input).
- Steps are already AWS service calls — Step Functions calls them directly, no Lambda needed as glue.
- Don't use it for: a linear 2-step process (just chain the calls), tight loops with thousands of iterations at sub-second latency (state transition cost/latency — use a Lambda or Express), or complex logic better expressed in code (put that in one Lambda).
Verification and troubleshooting¶
aws stepfunctions start-execution --state-machine-arn ... --input '{"...": "..."}'
aws stepfunctions describe-execution --execution-arn ...
aws stepfunctions get-execution-history --execution-arn ... --max-items 50
The console's visual graph highlights the failed state — start there.
States.TaskFailedwith no useful cause — the Lambda threw; the error type is the exception class, the cause is the message + stack. Add aCatchforStates.ALLto route failures somewhere you can inspect.States.Runtime/States.DataLimitExceeded— the state input/output exceeded 256KB. Pass references (an S3 key), not payloads. UseResultPath/OutputPathto trim what flows between states.The role defined for the function cannot be assumed/ AccessDenied — the state machine's execution role lacks permission to invoke the Lambda / call the service. Each integration needs its own IAM action.- Map state slow / throttled —
MaxConcurrencytoo high hitting downstream limits, or too low leaving it serial. Tune it; Distributed Map for huge item counts. - Standard execution cost surprise — per state transition billing; a
tight
Choice+Waitpolling loop racks up transitions. UseWaitwith a longer interval, or Express, orwaitForTaskToken(event-driven, no polling). - Express execution "succeeded" but did nothing — at-least-once semantics + a non-idempotent step that was retried, or async invocation where you didn't check the result. Check CloudWatch Logs (Express has no console history).
- Can't tell what a past execution did (Express) — enable full logging
(
ALLlevel, include execution data) on the state machine; Express keeps nothing by default.
Related tools and reading¶
- On-site: JSON formatter.
- Related posts: SQS vs SNS vs EventBridge, Lambda cold starts and how to cut them.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Cloud & AWS
AWS WAF: The Rules That Matter
AWS WAF filters HTTP requests at the ALB/CloudFront/API Gateway edge. Here's a sensible baseline Web ACL, how to tune with count mode, and the false-positive traps.
October 24, 2026An S3 Static Site Behind CloudFront
The right way to host a static site on AWS: a private S3 bucket, CloudFront with OAC in front, HTTPS via ACM, and SPA fallback routing. Plus the 403s that trip everyone.
October 24, 2026CloudWatch Logs and Metric Filters
CloudWatch Logs is cheap to write and expensive to forget about. Here's retention, turning error patterns into metrics and alarms, and Logs Insights for ad-hoc queries.
October 24, 2026