Idempotency in Practice
also called Safe Retry, Exactly-Once Effect
The full treatment — why retries make it mandatory, the four implementation patterns, the client-side bug that defeats it, and how Stripe made it a public contract.
Definition
An operation is idempotent when performing it many times has the same effect as performing it once. Not the same response necessarily — the same effect on state.
Why it matters
Because a client that sends a request and receives a timeout cannot distinguish "the request never arrived" from "it succeeded and the response was lost". There is no client-side cleverness that resolves this; the information does not exist.
So the client must either retry, risking a duplicate, or not retry, risking a lost operation. At any real volume both happen constantly — network failures during the response are routine, not exceptional. Idempotency is what makes the choice safe.
This is also why "at-least-once delivery" is the honest guarantee of every messaging system, and why idempotent consumers are not an optimisation but the thing that makes at-least-once usable.
Implementation patterns
Natural idempotence. The operation is a set rather than an increment: status = SHIPPED
rather than advance_status(). Repeating changes nothing. Cheapest by far, and frequently available
if the message carries the new value rather than a delta.
Idempotency key with stored result. The client generates a key per logical operation; the server records the key with the response and returns the stored response on a repeat. The key and the effect must be committed atomically, or a crash between them reintroduces the problem.
Conditional write. The update carries an expected version or state, and the database rejects it if the version has moved — fencing by another name. The rejection happens at the resource rather than in application logic, which is stronger.
Deduplication window. Record processed message identifiers and drop re-arrivals. Requires a retention decision, because storing every identifier forever is not viable and a duplicate arriving after the window will be processed.
Failure scenarios
The client regenerates the key on each retry. This is the most common defect and it silently defeats a perfectly correct server. The key must be generated when the operation is created — when the user commits to paying — and persisted with it, so every attempt including after a restart carries the same value.
Non-atomic recording. The effect commits, the process dies, the key is never recorded. The retry executes again.
Concurrent requests with the same key, where both pass the "have I seen this?" check before either writes. Needs a lock or a unique constraint.
Key reused with a different payload, returning the wrong cached response. Bind the key to a hash of the request.
The effect is outside the transaction. Exactly-once semantics in a stream processor guarantee effect within the framework's transactional boundary. An email, an HTTP call or a payment is outside it and will happen twice.
Industry example
Stripe exposes an Idempotency-Key header on mutating requests, stores the result against it, and
returns the original response on a repeat rather than merely rejecting the duplicate — so the client
learns the outcome. Keys expire after a documented window. It is versioned, documented and guaranteed,
which is what lets integrators build retry logic against it with confidence.
Trade-offs
Storage that grows with request volume and needs pruning. Additional latency for the key lookup on the hot path. A retention window that must be sized against the longest plausible retry, and stated explicitly rather than left as an implementation detail.
Against that: an entire class of duplicate-charge, duplicate-order and duplicate-message incidents becomes a non-event.
Interview question
A payments API supports idempotency keys. Duplicate charges still occur occasionally. The server implementation is verified correct. Where is the bug?
Look for the candidate to reach the client: the key is being generated inside the retry loop, or at HTTP request construction, rather than once per logical operation. A strong answer also proposes deriving the key deterministically from a stable business identifier, so it survives the client losing local state.