A timeout describes the wait, not the outcome
You click “Reserve”. A few seconds later, the request times out. Can you safely click again? The server may have written nothing, or it may have completed the write and lost the response. Those situations look identical to the client.
Before adding , answer a different question: how do you recognize the same user intent? Repeating an request does not answer that question. For an operation that creates a booking, generating a fresh on every attempt may create a fresh booking.
Rendering diagram…
Diagram source
sequenceDiagram
participant C as Client
participant A as API
C->>A: Booking, key attempt-42
A->>A: Store result
A--xC: Response lost
C->>A: Retry, same key
A->>C: Same resultA small runnable example
Here, means that repeating the same operation does not create another effect. Save this as retry.py and run python3 retry.py ( 3.11+). No or network is needed. This is deliberately a single-process model of the rule, not a complete payment or reservation mechanism.
responses: dict[str, tuple[int, str]] = {}
created: list[str] = []
def create_booking(key: str, seats: int) -> str:
if not key or type(seats) is not int or seats < 1:
raise ValueError("invalid input")
if key in responses:
previous_seats, booking_id = responses[key]
if previous_seats != seats:
raise ValueError("key reused with different input")
return booking_id
booking_id = f"booking-{len(created) + 1}"
created.append(booking_id)
responses[key] = (seats, booking_id)
return booking_id
first = create_booking("attempt-42", 2) # Imagine this response was lost.
second = create_booking("attempt-42", 2)
assert first == second
assert len(created) == 1
print(second, len(created))
try:
create_booking("attempt-42", 3)
except ValueError as error:
print(error)
else:
raise AssertionError("Conflicting input must be rejected")Expected output:
booking-1 1
key reused with different inputThe first call creates a result and it under the key. The second returns that result. If the same key arrives with a different seat count, we reject the conflicting input instead of pretending it repeats the previous intent.
Change one thing and observe the difference
Change attempt-42 to attempt-43 in the second call. The first == second should fail. You have demonstrated the distinction between retrying an operation and starting a new operation. the key. Now consider when the client should create it and how to preserve it between attempts.
A key does not replace . In a real system, its scope must include the user or and operation type. Knowing someone else's key must not reveal their booking.
Where this example stops being sufficient
An in-memory dictionary disappears on restart. Two processes have two dictionaries. requests can both see a missing entry and both perform the effect. A production implementation therefore needs durable storage and atomic conflict protection, not just an if statement.
Also distinguish a local write from a call to an external provider. A transaction in your database cannot undo a payment already collected elsewhere. Understand the provider's contract, status lookup and retry rules. Do not assume every key-bearing header offers identical guarantees.
Decisions to make before deployment
- What identifies the intent, and who preserves the key between attempts?
- What happens when the same key has different input?
- What happens under concurrent requests and after restart?
- How long is the result retained, and what does the client do afterwards?
- What is the retry limit, and how do you recognize an unknown outcome?
Work through these questions for one operation before adding a generic retry mechanism across the application. The important evidence is a single effect, not a count of successful HTTP responses.
Sources
CHECK YOUR INTUITION

