This page requires JavaScript to display.
F
Unpacking...
← Cloud Platforms
Cloud Platforms

AWS Step Functions for Data Engineers

Standard vs Express, state types, a full ETL state machine, .sync and callback integrations, data passing, Map and Distributed Map, error handling, triggering and monitoring.

Contents
1. What AWS Step Functions Is 2. State Types 3. Example: A Daily ETL Pipeline 4. Service Integration Patterns 5. Passing Data Between States 6. Map: Fan-Out over Many Items 7. Error Handling 8. Triggering, Testing & Monitoring 9. Common Interview Questions

1. What AWS Step Functions Is

AWS Step Functions orchestrates workflows as state machines defined in JSON (Amazon States Language, ASL). It calls AWS services directly (Glue, EMR, Athena, Lambda, ECS, Batch, SNS, DynamoDB and 200+ others), waits for them, retries failures, and shows each execution visually. It is serverless: no scheduler to run.

Standard workflowExpress workflow
Maximum durationUp to 1 yearUp to 5 minutes
Execution semanticsExactly-once state transitionsAt-least-once (async) / at-most-once (sync)
HistoryFull visual history in the consoleLogs in CloudWatch
PricingPer state transitionPer request and duration
Use forData pipelines, long jobs, human approvalHigh-volume, short event processing

2. State Types

StatePurpose
TaskDo work: call a service API or an activity
ChoiceBranch on input values
ParallelRun fixed branches at the same time, then join
MapRun the same steps for every item in a list (inline) or millions of S3 objects (distributed)
WaitPause for a time or until a timestamp
PassTransform or inject data, or act as a placeholder
Succeed / FailEnd the execution

3. Example: A Daily ETL Pipeline

Validate the file with Lambda, run a Glue job and wait for it, check data quality, refresh Athena partitions, and notify. Every failure goes to one alert state.

{
  "Comment": "Daily transactions load",
  "StartAt": "ValidateFile",
  "States": {
    "ValidateFile": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": { "FunctionName": "file-validator", "Payload.$": "$" },
      "ResultSelector": { "rows.$": "$.Payload.rows", "valid.$": "$.Payload.valid" },
      "ResultPath": "$.validation",
      "Next": "IsValid",
      "Catch": [{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error", "Next": "NotifyFailure" }]
    },
    "IsValid": {
      "Type": "Choice",
      "Choices": [{ "Variable": "$.validation.valid", "BooleanEquals": true, "Next": "RunGlueJob" }],
      "Default": "NotifyFailure"
    },
    "RunGlueJob": {
      "Type": "Task",
      "Resource": "arn:aws:states:::glue:startJobRun.sync",
      "Parameters": {
        "JobName": "raw-to-silver-transactions",
        "Arguments": { "--run_date.$": "$.run_date" }
      },
      "ResultPath": "$.glue",
      "Retry": [{
        "ErrorEquals": ["Glue.ConcurrentRunsExceededException", "States.TaskFailed"],
        "IntervalSeconds": 60, "MaxAttempts": 2, "BackoffRate": 2.0
      }],
      "Catch": [{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error", "Next": "NotifyFailure" }],
      "Next": "AddPartition"
    },
    "AddPartition": {
      "Type": "Task",
      "Resource": "arn:aws:states:::athena:startQueryExecution.sync",
      "Parameters": {
        "QueryString.$": "States.Format('ALTER TABLE silver.transactions ADD IF NOT EXISTS PARTITION (dt = \\'{}\\')', $.run_date)",
        "WorkGroup": "data-eng"
      },
      "ResultPath": null,
      "Next": "NotifySuccess"
    },
    "NotifySuccess": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sns:publish",
      "Parameters": {
        "TopicArn": "arn:aws:sns:ap-south-1:111122223333:pipeline-events",
        "Message.$": "States.Format('Loaded {} rows for {}', $.validation.rows, $.run_date)"
      },
      "End": true
    },
    "NotifyFailure": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sns:publish",
      "Parameters": {
        "TopicArn": "arn:aws:sns:ap-south-1:111122223333:pipeline-alerts",
        "Message.$": "States.JsonToString($)"
      },
      "Next": "Failed"
    },
    "Failed": { "Type": "Fail", "Error": "PipelineFailed" }
  }
}

4. Service Integration Patterns

PatternResource suffixBehaviour
Request-response(none), e.g. arn:aws:states:::glue:startJobRunCall the API and move on immediately
Run a job.syncStart the job and wait until it finishes; fail the state if the job fails
Callback.waitForTaskTokenPass a task token to something external (a human approval, an on-prem system); the workflow waits until it calls SendTaskSuccess/Failure
# a worker (or approval UI) completes a waiting callback task
import boto3, json
sfn = boto3.client("stepfunctions")
sfn.send_task_success(taskToken=token, output=json.dumps({"approved": True, "by": "risk-team"}))
# or: sfn.send_task_failure(taskToken=token, error="Rejected", cause="Totals do not reconcile")

5. Passing Data Between States

With the classic JSONPath query language, each state filters its input and output in a fixed order:

state input
  → InputPath        select part of the input           "$.order"
  → Parameters       build the API request              {"JobName": "x", "Arguments": {"--id.$": "$.id"}}
  → (task runs)
  → ResultSelector   keep only useful result fields     {"status.$": "$.JobRunState"}
  → ResultPath       where to put the result in input   "$.glue"   (null = discard the result)
  → OutputPath       select what passes to next state   "$"

6. Map: Fan-Out over Many Items

"LoadEachTable": {
  "Type": "Map",
  "ItemsPath": "$.tables",
  "MaxConcurrency": 5,
  "ItemSelector": { "table.$": "$$.Map.Item.Value", "run_date.$": "$.run_date" },
  "ItemProcessor": {
    "ProcessorConfig": { "Mode": "INLINE" },
    "StartAt": "LoadTable",
    "States": {
      "LoadTable": {
        "Type": "Task",
        "Resource": "arn:aws:states:::glue:startJobRun.sync",
        "Parameters": { "JobName": "generic-table-load",
                        "Arguments": { "--table.$": "$.table", "--run_date.$": "$.run_date" } },
        "End": true
      }
    }
  },
  "Next": "Done"
}

Distributed Map processes very large datasets: it reads items straight from S3 (a prefix listing, CSV, JSON lines or an S3 Inventory manifest) and runs up to thousands of child workflows in parallel:

"ProcessAllFiles": {
  "Type": "Map",
  "ItemReader": {
    "Resource": "arn:aws:states:::s3:listObjectsV2",
    "Parameters": { "Bucket": "acme-lake-prod", "Prefix": "raw/backfill/" }
  },
  "MaxConcurrency": 500,
  "ToleratedFailurePercentage": 1,
  "ItemBatcher": { "MaxItemsPerBatch": 50 },
  "ItemProcessor": {
    "ProcessorConfig": { "Mode": "DISTRIBUTED", "ExecutionType": "EXPRESS" },
    "StartAt": "ConvertBatch",
    "States": {
      "ConvertBatch": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
                        "Parameters": { "FunctionName": "convert-to-parquet", "Payload.$": "$" }, "End": true }
    }
  },
  "ResultWriter": { "Resource": "arn:aws:states:::s3:putObject",
                    "Parameters": { "Bucket": "acme-lake-prod", "Prefix": "_runs/backfill/" } },
  "End": true
}

7. Error Handling

FieldMeaning
ErrorEqualsWhich errors the rule matches: service errors, States.TaskFailed, States.Timeout, States.ALL
IntervalSeconds / BackoffRateFirst wait and its multiplier (e.g. 60, 120, 240 seconds)
MaxAttemptsRetries after the first attempt (0 = never retry)
MaxDelaySeconds / JitterStrategyCap the wait and add randomness to avoid retry storms
Catch + ResultPathRoute to a fallback state, keeping the original input and adding the error under a path
TimeoutSeconds / HeartbeatSecondsFail a task that runs too long or stops reporting

Order matters: Retry rules are tried first, and Catch runs only after retries are exhausted. Put specific errors before States.ALL.

8. Triggering, Testing & Monitoring

aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:ap-south-1:111122223333:stateMachine:daily-load \
  --name load-2026-10-26 \
  --input '{"run_date": "2026-10-26", "bucket": "acme-lake-prod", "key": "raw/txn/2026-10-26.csv.gz"}'

aws stepfunctions describe-execution --execution-arn <execution-arn>
aws stepfunctions list-executions --state-machine-arn <arn> --status-filter FAILED
ChooseWhen
Step FunctionsAWS-native, event-driven pipelines, serverless, strong retries and visual history
Amazon MWAA (Airflow)Complex schedules and backfills, many non-AWS systems, existing Airflow skills
Glue workflowsSimple chains of Glue jobs and crawlers only

9. Common Interview Questions

QuestionShort answer
Standard vs Express?Standard: up to a year, exactly-once transitions, full history, priced per transition. Express: up to 5 minutes, high volume, at-least-once, priced per request and duration
How does Step Functions wait for a Glue job?With the .sync integration (glue:startJobRun.sync), which starts the job and waits for its final state
What is the callback pattern?.waitForTaskToken passes a token to an external process; the workflow pauses until SendTaskSuccess or SendTaskFailure is called with that token
How do you process a million S3 files?Distributed Map reading from S3, batching items, high MaxConcurrency, Express child workflows, and a tolerated failure percentage
How do you avoid huge payloads between states?Pass references (S3 keys, IDs) instead of data; state input/output is limited to 256 KB
Retry vs Catch?Retry re-attempts the same state with backoff; Catch routes to another state after retries are exhausted