Architecture and State
Transactions and Idempotency Keys
Make retries safe by defining one atomic state transition and recording the request that caused it.
Lesson 2 of 6 in the recommended order · About 25 min (estimate)
On this page
Outcome
Design an idempotent command whose duplicate delivery returns the original result.
Why it matters
At this level, code is judged by how safely it changes under load, failure, and team ownership. The technique in this lesson makes an important boundary visible enough to test and review.
Concept
At-least-once delivery is normal. Store an idempotency key and the resulting outcome in the same transaction as the state change. A retry reads that record instead of repeating the effect.
Read the code
class Ledger:
def __init__(self):
self.results = {}
self.balance = 0
def credit(self, key, amount):
if key in self.results:
return self.results[key]
self.balance += amount
self.results[key] = self.balance
return self.balance
ledger = Ledger()
print(ledger.credit("req-7", 5), ledger.credit("req-7", 5))
This in-memory example models the observable duplicate result in one task; the dictionary check and balance update are not an atomic database transaction and are not safe against concurrent workers. A production implementation needs a uniqueness constraint and one transaction covering both the state change and stored response.
Read from the public behavior inward: identify the input boundary, the decision, and the observable result before studying syntax.
Trace an original request and its retry
One key replays one stored result
Two different models: The bracket is a proposed database design. The panels trace the single-task in-memory Ledger and separate supplemental calls.
The dictionary example has no transaction, uniqueness constraint, persistence or concurrency protection. Its two printed calls are sequential.
Conceptual database transaction · not executed by Ledger
Begin one database transaction for this command.Claim a uniquely constrained request key within its defined scope.Apply the protected database state change.Store the result needed to replay this request.
Transaction commits
Commit the key, state change and replay result together. Return the stored outcome after successful commit.
Transaction aborts
If the transaction aborts, none of these database writes commits. External effects need separate coordination; this bracket does not roll them back.
1 · First call in the listing
credit("req-7", 5)
Key absent: take the update branch
Before- balance = 0; results = {}
Apply once- balance += 5 gives 5
Record- results["req-7"] = 5
Return- 5; final balance = 5
The first call changes the in-memory state and saves its result.
2 · Retry in the same listing
credit("req-7", 5)
Same key found: return before balance += amount
Before- balance = 5; results = {"req-7": 5}
Replay- Return results["req-7"] = 5
Effect- No second credit; balance remains 5
Printed together- 5 5
The second call returns early using the same key.
Supplemental · replay is not current balance
New key after those calls- credit("req-8", 2) returns 7; balance becomes 7.
Retry old key- credit("req-7", 5) still returns 5; balance remains 7.
Changed amount under old key- credit("req-7", 99) also returns 5. This model ignores the amount once the key exists.
Contract decision- A real API must define key scope, retention and how changed request data under the same key is handled.
These extra calls are demonstrations, not part of the printed listing.
Production boundary · concurrency and failure
Concurrent arrivals- Two workers can both observe an absent key. A dictionary check is not an atomic claim.
Database duplicate handling- A uniqueness conflict or concurrent transaction needs explicit handling: wait or retry as appropriate, then replay the committed record.
Commit uncertainty- If commit succeeds but the response is lost, a retry with the same key reads the saved result.
Scope- This design protects the coordinated database state change. It does not guarantee exactly-once network delivery or arbitrary external effects.
Database behavior must be implemented and tested separately.
Predict the output
Predict the exact output before running the example.
Check your prediction
It prints 5 5, and the balance changes once. The stored result makes the duplicate response stable too.
Modify the code
Change one valid input into the nearest invalid or overloaded case. Write down which layer should reject it and what the caller should observe.
Review the change
Keep the failure at the narrowest boundary that owns the rule. Preserve a stable return value or exception contract so callers do not need to inspect implementation details.
Debug the bug
Checking for a key and changing state in separate transactions leaves a race where two workers both pass the check. They must commit atomically.
Try it yourself
Complete the focused implementation and run its deterministic checks.
Loading this exercise…
Practical challenge (optional)
Specify the unique constraint, transaction boundary, and response replay for an idempotent import command.
Sign in to track your progress on this exercise.
AI collaboration
Checkpoint
- Which operations must be committed in one transaction for an idempotent command?
- Why is a check followed by a separate write unsafe with two workers?
- Why store the original response as well as the idempotency key?
Answers
- The protected state change, the unique idempotency record, and the outcome needed to replay the response.
- Both workers can observe the key as absent before either writes, then both apply the effect.
- A duplicate can return the same observable result without repeating the effect or inventing a different response.
Sign in to track your progress on this exercise.
Summary and next step
You made the boundary explicit, predicted its behavior, tested a deterministic implementation, and examined its failure mode. Continue to the next lesson to combine this technique with a wider application or production constraint.