The Problem
Timeouts are indistinguishable from failures:
client → POST /payments → server PROCESSES (charged $20)
✗ response lost in network
client sees timeout, retries → SECOND CHARGE
POST has no natural idempotency; blind retry is unsafe;
no-retry means every transient hiccup fails the user's checkout
The Pattern
Client generates a unique key per logical operation and sends it with the request:
POST /payments
Idempotency-Key: 8f3c9a2e-... (client-generated UUID)
{ amount: 2000, currency: "usd", ... }
server flow:
┌──────────────────────────────────────────────────────┐
│ key seen before? │
│ ├─ first execution finished → replay STORED result │
│ ├─ execution IN FLIGHT → 409/425 or wait │
│ └─ never seen → execute, store │
└──────────────────────────────────────────────────────┘
retry with same key = same logical operation = same outcome,
regardless of how many times it arrives
The key converts “another request” into “the same request, again.”
Server-Side Storage Design
idempotency_keys table:
key TEXT PRIMARY KEY
request_hash BYTES ← detect KEY REUSE w/ different payload
status pending | completed | failed
response_code INT
response_body BYTES
expires_at TIMESTAMP ← TTL cleanup, e.g. 24h
insert-if-not-exists (unique constraint) provides the race safety:
two concurrent retries → one wins INSERT, other waits/replays
storage cost: response bodies × traffic × retention —
cap body size, expire aggressively, store only for mutating verbs
Key Lifecycle Rules
| Rule | Why |
|---|---|
| Client generates (UUID v4) | Server can’t distinguish “new op” from “retry” |
| One key per logical attempt | Reusing across different ops = wrong result replay |
| Same payload on retry | Mismatched payload + same key = client bug → 422 |
| Scope keys per resource/user | Prevent cross-tenant key collision games |
| Expire stored results (24–72h) | Bounded storage |
Where It Applies
mandatory: payments, transfers, order creation, seat booking
(any POST whose duplicate = real-world harm)
optional: likes, view counts (dupes harmless — skip machinery)
not needed: PUT/DELETE (already idempotent), GET
scope the mechanism where duplicates hurt; don't tax everything
Interaction With Retries
retry policy: on timeout/5xx → retry SAME key, backoff
on 4xx → do NOT retry (client error)
the key makes the retry safe; the policy decides if it happens.
both halves required — key without retry policy is decoration
Interview Framing
Payment/order designs get probed here: “user double-clicks buy.” Weak answers add client-side disable buttons (UI-only fix). Scored answer: idempotency key end-to-end — generation, unique-constraint race handling, stored-response replay, expiry. Mentioning Stripe-style Idempotency-Key headers shows you know this is industry-standard machinery, not cleverness.
Premium Content
Unlock Idempotency Keys and all premium lessons with a subscription.
All premium lessons
Ad-free experience
Priority support
From ₹199.99/year — See plans