Networks drop responses, clients time out and retry, and a retried write can run twice. An idempotent API makes the repeat produce the same effect as the first call, so a client can safely retry without creating duplicate orders, payments or messages.
Before you start
You should understand request lifecycles and basic storage. This article is conceptual with small illustrations rather than a full implementation.
Step-by-step walkthrough
Step 1: Accept a client-generated key
The client sends an idempotency key — a unique value per logical operation, not per attempt — usually in a header. On a repeat with the same key, the server recognizes the operation and does not perform the side effect again. The key must be generated once and reused across retries.
Step 2: Store the result, not just a flag
Persist the key together with the outcome, so a retry returns the original response rather than re-executing. If you store only “seen”, the retry has no body to return and can return an error or a different result. Store the key, the status and the response, and make the store atomic with the side effect.
Step 3: Scope and expire the key
Scope keys to the caller and operation so two clients cannot collide, and expire them after a window long enough to cover retries. A key that is too broad lets one client’s retry match another’s, and a key kept forever grows the store without benefit.
Worked scenario
The server records the key and replays the stored response.
POST /payments
Idempotency-Key: 8f2c1a
-> first request: charge created, store {key: 8f2c1a, status: 201, body}
-> retry, same key: return stored {status: 201, body}, do not charge againWalk through the example
The first request performs the charge and stores the key with its response. A retry with the same key finds the record and returns the stored response, so the customer is charged once. If the two requests race, a unique constraint on the key lets one win and the other read the stored result.
Common mistake
Generating the key per attempt, which defeats the purpose because each retry looks like a new operation. Another is storing only a marker and then returning a fresh response, which may differ from the original. Also, a key with no unique constraint lets concurrent retries both proceed.
Verify the behavior
Send the same request twice with one key and assert a single side effect and identical responses. Send concurrent requests with the same key and confirm a unique constraint serializes them. Assert a different key creates a separate effect, and that an expired key allows a new operation.
Interview exercise
Where should the idempotency key be generated, the client or the server?
Answer and reasoning
The client, because it knows the logical operation and whether a retry is a repeat of the same intent. A server-generated key is different on each request, so it cannot identify a repeat. The client generates one key per operation and reuses it across every retry and transport attempt.
Continue learning
Compare the messaging version in Idempotency keys and API resource contracts. Read the Stripe idempotency guidance and try the System design interview questions.