AWS Builder Center
My Step Functions Workflow Kept Losing Data and AWS Never Threw an Error.

My Step Functions Workflow Kept Losing Data and AWS Never Threw an Error.

My state machine was green, my executions were succeeding, and my data was quietly disappearing. Here is the default behavior that causes it, the five-stage mental model that explains it, and the one-line ResultPath fix.

I am learning AWS Step Functions in public, and this article is about the first problem that genuinely confused me: my state machine kept losing data, and nothing was throwing an error.
If you are new to Step Functions, I think this is the wall most of us hit first. Here is how I hit it, why it happens, and the mental model that made it click.
First, what is Step Functions and why bother?
AWS Step Functions is a serverless orchestration service. You define a workflow as a series of states (do this, then that, retry if it fails, branch if a condition is met), and Step Functions runs it, tracks it, and shows you exactly where each execution is.
The honest way to explain why it matters is to describe life without it. Before I started learning it, my mental picture of a multi-step process was one big Lambda function calling other Lambdas, with try/except blocks everywhere and print statements as my only visibility. That works until something fails halfway. Then you are asking questions like: did step three run? Is it safe to retry the whole thing? Where is the data from step two?
Step Functions takes that coordination logic out of your code and makes it a first-class thing you can see. Some common uses:
  • Order processing pipelines, where payment, inventory, and notification steps must happen in a specific order
  • Data processing workflows that fan out work in parallel and gather results
  • Human approval flows, where the workflow pauses for hours or days waiting for someone to click approve
  • Anything with retries, because Step Functions can retry a single failed step with backoff instead of rerunning everything
The workflow is defined in a JSON-based language called Amazon States Language (ASL), and you can build it visually in Workflow Studio in the console, which is where I do most of my learning.
The core job of the service, then, is passing data between states. Which is exactly where I ran into trouble.
The setup
I had a simple state machine. It receives an order event as input, passes it to a Lambda function that enriches it with some extra data, and moves on. My input looked like this:
{
"orderId": "ord-1234",
"customerId": "cust-5678",
"amount": 15000,
"currency": "NGN"
}
My Lambda (I named it  sfn-enricher-learning ) looked up some customer details and returned this:
{
"customerTier": "gold",
"discountEligible": true
}
Simple enough. I wired the Task state up in Workflow Studio, ran an execution, and checked the output of the enrichment step.
{
"customerTier": "gold",
"discountEligible": true
}
My order was gone. The  orderId , the  customerId , the  amount . All of it. The Lambda output had completely replaced my input, and every state after this one had no idea what order it was processing.
No error. No warning. The execution was green. That is the part that got me. A failing state machine tells you something is wrong. This one was quietly succeeding while throwing away the data I needed.
Why this happens
Step Functions has a default behavior that nobody warns you about loudly enough: the output of a Task state replaces its input. It does not merge. It replaces.
I started calling this the clobbering problem, because that is what it felt like. My Lambda result was clobbering my state.
Think of it like a shared whiteboard in an office. Each state walks up, reads what is on the board, does its work, then wipes the board clean and writes only its own result. The next state never sees what was there before. If you want the old notes to survive, you have to say so explicitly.
The mental model: five doors your data walks through
What fixed this for me was learning that data does not just flow "into" and "out of" a state. Inside every Task state, your data passes through up to five processing stages, in a fixed order:
InputPath -> Parameters -> (task runs) -> ResultSelector -> ResultPath -> OutputPath
Each one is optional, and each one answers a different question:
  1. InputPath: of everything in the current state, what portion should this state even see?
  1. Parameters: how should I reshape that portion into the exact payload my task wants?
  1. ResultSelector: of everything the task returned, what portion do I care about?
  1. ResultPath: where in my original input should I place that result?
  1. OutputPath: of the combined result, what should I pass to the next state?
The one that solves the clobbering problem is ResultPath. Its default value is  $ , which means "put the result at the root," which really means "replace everything." That default is the whole problem.

The one-line fix

I changed my Task state to place the Lambda result under a new key instead of at the root:
{
"EnrichOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "sfn-enricher-learning",
"Payload.$": "$"
},
"ResultSelector": {
"customerTier.$": "$.Payload.customerTier",
"discountEligible.$": "$.Payload.discountEligible"
},
"ResultPath": "$.enrichment",
"Next": "CheckDiscount"
}
}
The important line is  "ResultPath": "$.enrichment" . It tells Step Functions: keep my input exactly as it is, and attach the task result under a new  enrichment  key.
Now the output of the same step looks like this:
{
"orderId": "ord-1234",
"customerId": "cust-5678",
"amount": 15000,
"currency": "NGN",
"enrichment": {
"customerTier": "gold",
"discountEligible": true
}
}
The order survived. The enrichment is there. Every downstream state can see both. One line.
You will also notice  ResultSelector  in there. When you invoke Lambda through the optimized integration, the actual function result comes back wrapped inside a  Payload  field along with metadata like status codes. ResultSelector is how I pull out just the fields I want before ResultPath files them away. Without it, my nice clean  enrichment  key would be full of invocation metadata I never asked for.

Why I think this matters beyond tutorials

When I shared an earlier stage of this learning journey, a commenter raised a point that stuck with me: in production, silent data loss is far more dangerous than a loud failure. A state machine that crashes gets fixed the same day. A state machine that quietly drops fields can run for weeks while a downstream system makes decisions on incomplete data.
The default ResultPath behaviour is exactly that kind of trap. It is not a bug, it is documented, but it fails silently, and silence is the expensive kind of failure.
My rule for myself now: every Task state gets an explicit ResultPath, even when the default would work. Writing it down forces me to answer the question "what happens to my input here?" instead of letting the default answer it for me.
What I would tell past me
  1. The output of a state replaces its input unless you say otherwise. Tattoo this somewhere.
  1. Learn the five-stage pipeline (InputPath, Parameters, ResultSelector, ResultPath, OutputPath) before writing your second state machine. It turns every data-shape bug from a mystery into a checklist.
  1. Test with the data inspector in Workflow Studio. You can see the shape of your data at each of the five stages without running a full execution, and it would have saved me an evening.
I am still working through error handling next, deliberately making this same Lambda throw errors so I can learn Retry and Catch properly. That will probably be its own article.
What was your first Step Functions gotcha? I would love to hear it, because I suspect ResultPath is not the only silent trap waiting for me.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article