Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Idempotency Keys
HLD

Idempotency Keys

Making non-idempotent operations safe to retry — the pattern behind every reliable payment API.

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

RuleWhy
Client generates (UUID v4)Server can’t distinguish “new op” from “retry”
One key per logical attemptReusing across different ops = wrong result replay
Same payload on retryMismatched payload + same key = client bug → 422
Scope keys per resource/userPrevent 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.

My Private Notes

Notes are auto-saved locally to this device.