System Design Interview Preparation: Learn How Idempotency Keys Prevent Duplicate Orders, Double Payments, and Unsafe API Retries Using a Real-World E-Commerce Example.
Tagline: Retries Are Inevitable. Duplicate Transactions Shouldn't Be.
A customer taps "Pay." The signal drops. The app tries again. If your server treats the second request as new, the customer pays twice. One small idea prevents it.
The problem at Varastra
Varastra is an online store for Indian ethnic wear. It sells sarees, kurtas and lehengas. Ivan is paying for a saree on a train. The signal drops after he taps "Pay."
The app gets no reply. It sends the request again. The server did get the first one. It charges Ivan twice.
Zoe, an engineer at Varastra, gets the complaint. Max, a senior engineer, asks her: "How does the server tell a retry from a new order?" She has no answer yet.
The diagram at a glance
The diagram shows a client and a server, with six numbered steps. It has two sides.
- Without a key. The server charges once. The client sees a timeout and retries. The server charges again. Result: two orders created.
- With a key. The client sends a key with the first request. After the timeout, it sends the same key again. The server sees the key was already used and returns the result of the first attempt. Result: one order created.
The diagram's result comes from a small Python demo that sends the same order twice. It shows the idea in memory only.
What "idempotent" means
An operation is idempotent if doing it twice has the same effect as doing it once.
- "Set the delivery address to X" is idempotent. Do it twice and the address is still X.
- "Add 500 to the balance" is not. Do it twice and you add 1,000.
Payments and orders are naturally not idempotent. We must make them safe.
The idea of an idempotency key
The app makes a unique key for each action, for example a random ID. It sends the key with the request. It sends the same key on every retry.
The server keeps a record: "Key k-1 was already done. Here is the saved answer."
- First request with the key: do the work, save the result, reply.
- Second request with the same key: do not repeat the work. Reply with the saved result.
The code
class PaymentServer:
def __init__(self):
self.charges = [] # money actually taken
self.saved = {} # idempotency key -> (request, response)
def pay(self, key, order_id, amount):
request = (order_id, amount)
if key in self.saved:
saved_request, saved_response = self.saved[key]
if saved_request != request:
return {"status": 422, "error": "key reused with a different request"}
return saved_response # same key, same request: replay the answer
self.charges.append(request)
response = {"status": 200, "charge_id": len(self.charges)}
self.saved[key] = (request, response)
return response
server = PaymentServer()
print(server.pay("k-1", "VA-100", 49)) # first try
print(server.pay("k-1", "VA-100", 49)) # retry after a timeout
print(server.pay("k-1", "VA-100", 90)) # same key, different amount
print("charges made:", len(server.charges))
Output:
{'status': 200, 'charge_id': 1}
{'status': 200, 'charge_id': 1}
{'status': 422, 'error': 'key reused with a different request'}
charges made: 1
The retry got the same answer, and only one charge was made. A key reused with a different amount was rejected. This is a toy model. It does not test any real payment product.
What the draft standard says
An Internet-Draft describes an Idempotency-Key HTTP header. The version read for this article is draft-ietf-httpapi-idempotency-key-header-08, dated 22 October 2026. It is an active draft, not a final standard, and it expires on 25 April 2027. Details may change, so check the latest version before you rely on it.
What the draft says:
- It can make non-idempotent methods such as POST or PATCH fault-tolerant.
- The key "MUST be unique" and must not be reused with a different request payload. It suggests UUIDs or similar random values.
- Error cases: 400 if a required key is missing. 409 if a retry arrives while the first request is still being processed. 422 if a key is reused with a different payload.
- A service should publish how long it keeps keys.
Design questions to answer
These are suggestions, not rules.
- How long do you keep keys? Long enough to cover real retries. Not forever. Publish the rule.
- What if two copies arrive at the same moment? Only one may run. The other waits or gets a conflict reply.
- What if the first try failed halfway? Decide what the saved record says, and whether a retry may continue.
- Where is the record stored? If it lives only in the memory of one server, another server will not know it.
Takeaways
- Retries are normal on bad networks, so repeated requests must be safe.
- An idempotency key lets the server spot a repeat and return the saved answer.
- Use the same key on every retry of the same action.
- The Idempotency-Key header is still a draft. Check the latest version first.
