Reference · Reliability

Idempotency & Retries

IdempotencyRetriesStatus codesHTTP methodsREST

Published

The compressed essence. One page to glance at before an interview or a design review.

Quick definitions

Safe
Read-only intent — no intended state change. GET, HEAD, OPTIONS. → “Safe” is about intent, not “secure” and not “side-effect-free” (a GET may still log).
Idempotent
N identical requests ≡ one request’s effect on state. GET, HEAD, OPTIONS, PUT, DELETE. → The property that makes a request safe to retry.
POST — not idempotent
Each call can create another resource / charge again. → Needs an Idempotency-Key to be retried safely.
PATCH — not guaranteed
Depends entirely on the patch document. → A “set field to X” patch is idempotent; an “increment by 1” patch is not.

Method idempotency at a glance

MethodIdempotent?Notes
GETyesPure read.
PUTyesFull replace — repeated puts converge to the same state.
DELETEyesReturn 204, or 404 when already gone — the resource is absent either way.
POSTnoEach call can create / charge again — needs an Idempotency-Key.
PATCHdependsIdempotent only if the patch doc is (e.g. “set X” yes, “increment” no).

The Idempotency-Key pattern

Make a non-idempotent operation safe to retry by deduplicating on a client-supplied key. Stripe.

1Client generates a unique key per logical operation (UUID v4) — not per HTTP attempt.
2Client sends it in the Idempotency-Key request header.
3Server, before executing, looks up the key in a durable store.
4Miss → execute inside a transaction that also persists the key + resulting status code + response body + a fingerprint of the request.
5Hit → return the stored response without re-executing.
6Keys expire after a TTL (Stripe ≈ 24h).
7If a retry’s body differs from the stored fingerprint → reject (key reuse for a different request).

Implementation checklist

Durable store
A DB table or Redis with a TTL — must survive a process crash mid-request.
Persist the whole outcome
Status code + response body + request fingerprint, so replays are byte-identical and reuse is detectable.
Concurrency control
Enforce a UNIQUE constraint or lock on the key so two in-flight requests can’t both execute — the loser waits or gets 409.
Scope the key
Per endpoint + per account, so collisions and cross-tenant leakage can’t happen.
Apply selectively
Only on unsafe / non-idempotent operations — payments, order creation, message sends. Skip it for reads and naturally idempotent writes.

Idempotency ≠ exactly-once

Exactly-once delivery is impossible in a distributed system. What you can build is exactly-once effect:

=exactly-once effect  =  at-least-once delivery  +  idempotent processing (dedup)

Retry strategy

Retry only what’s safe
Idempotent methods, or non-idempotent ones guarded by an Idempotency-Key. Never blindly retry a bare POST.
Exponential backoff + jitter
Spread retries over growing, randomized intervals to avoid synchronized retry storms (thundering herd).
Honor Retry-After
When the server tells you when to come back (429 / 503), obey it.
Cap attempts
Bound total retries; fail fast rather than hammering a dead dependency.
Pair with circuit breakers
Stop retrying entirely once a downstream is clearly down; let it recover.