Skip to main content
Learning Center
Python Programming

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

  1. Begin one database transaction for this command.
  2. Claim a uniquely constrained request key within its defined scope.
  3. Apply the protected database state change.
  4. 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.

The two listing calls share req-7 and apply one in-memory credit. The database bracket is a separate required design, not a guarantee supplied by this Python dictionary. Supplemental calls demonstrate why the original result must be stored.

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

  1. Which operations must be committed in one transaction for an idempotent command?
  2. Why is a check followed by a separate write unsafe with two workers?
  3. Why store the original response as well as the idempotency key?
Answers
  1. The protected state change, the unique idempotency record, and the outcome needed to replay the response.
  2. Both workers can observe the key as absent before either writes, then both apply the effect.
  3. 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.

learning.goultergroup.com

The interactive parts of this page have not loaded. Reading and links still work; reload the page to try again.