Handling Partial Failures
In distributed systems, 'it failed' is rarely the whole truth. Some calls succeed, some do not, and the gap needs handling.
What you'll learn
- Recognise that distributed calls have three outcomes, not two
- Use idempotency to make retries safe after an unknown result
- Reconcile state when you cannot tell what happened
- Avoid duplicate side effects from naive retries
7 min
Success, failure, and unknown
A local function returns or it throws. A network call has a third outcome: unknown. The request may have reached the server, been processed, and the response lost on the way back — leaving you unsure whether the work happened. Treating unknown as plain failure is a common and costly mistake, because a blind retry can repeat an action that already succeeded.
Design for all three states. When a call times out or the connection drops mid-flight, your code does not know the server's outcome and must recover safely without assuming either result. This ambiguity is the heart of partial-failure handling.
Idempotency makes retries safe
The cleanest answer to the unknown case is idempotency: design operations so performing them twice has the same effect as once. Then, after an ambiguous result, you can simply retry without fear of duplication. A common technique is an idempotency key — a unique value the client sends so the server recognises and de-duplicates a repeat.
POST /charges
Idempotency-Key: order-7741-attempt
# retried with the SAME key returns the
# original result, no second chargeWhere an API supports such keys, use them on every non-idempotent write so retries are always safe.
Reconcile when you cannot tell
Sometimes you cannot retry safely and must instead discover what actually happened. Reconciliation means checking the authoritative state — querying the resource to see whether it was created, or comparing your records against the server's — and correcting any drift. A scheduled reconciliation job can catch the rare cases that slip through real-time handling.
This matters most for operations with real-world effects, such as payments or record creation, where a duplicate or a missed action has genuine consequences. Reconciliation is the safety net beneath retries: it assumes some ambiguity will always escape and provides a way to clean up afterwards.
Beware naive retry loops
A retry loop written without idempotency is a duplication engine. If the first attempt actually succeeded but its response was lost, each retry creates another record or repeats another side effect. The user sees one action; the system records several. These bugs are nasty because they only appear under the exact timing of a lost response.
Guard every retry with idempotency or a reconciliation check, and cap the number of attempts. Combined with sensible timeouts and deadlines, this turns the unknown outcome from a source of corruption into a routine, recoverable event.
Key takeaways
- Network calls have three outcomes: success, failure, and unknown
- Make writes idempotent so retries after an unknown result are safe
- Use idempotency keys where the API offers them
- Reconcile against authoritative state when you cannot retry safely
- Never retry a non-idempotent write blindly
FAQ
What exactly is an idempotency key?
A unique value the client attaches to a request so the server can recognise a retry of the same operation and return the original result instead of performing the action again.
If a request times out, did it fail?
You cannot be sure. The server may have completed the work before the response was lost. Treat a timeout as unknown and recover with idempotency or reconciliation, not as a definite failure.
Do I need reconciliation if my writes are idempotent?
Idempotency handles most cases, but a periodic reconciliation job is a valuable backstop for operations with real consequences, catching the rare ambiguity that slips past real-time handling.
Ready to build?
Read the API reference, grab the OpenAPI spec, and ship a resilient integration.