Idempotent, Explained: Why It Matters for APIs and Retries
What idempotent means in practice — how it shapes HTTP method semantics, why it makes retries safe over flaky networks, and how the idempotency key pattern stops duplicate charges.
A request times out. You don’t know whether the server got it. Do you retry? Whether that’s safe comes down to one word: idempotent.
What idempotent actually means
An operation is idempotent if performing it many times produces the same result as performing it once. The state of the system after one call is identical to the state after ten identical calls.
The classic example is setting a value. x = 5 is idempotent — run it once or a hundred times and x is 5 either way. Compare that to x = x + 1, which lands somewhere different every time. The first is idempotent; the second is not.
Note what idempotent does not mean. It does not mean “no effect.” DELETE /users/42 changes the world the first time — the user is gone. But running it again leaves the system in the same state: the user is still gone, and the outcome is the same. The effect doesn’t repeat. That’s the distinction that matters.
This is also different from safe, a related term that gets confused with it. A safe operation has no side effects at all — it only reads. A GET request is safe: it shouldn’t change anything. Every safe operation is idempotent (reading twice changes nothing), but not every idempotent operation is safe. DELETE is idempotent but definitely not safe.
How HTTP methods encode this
HTTP method semantics are built on these two properties, and the spec defines which methods are which.
- GET — safe and idempotent. It reads a resource. Repeating it returns the same thing and changes nothing.
- PUT — idempotent, not safe. It replaces a resource with the body you send.
PUT /users/42with the same payload always leaves user 42 in the same final state, no matter how many times you send it. - DELETE — idempotent, not safe. Deleting an already-deleted resource leaves it deleted.
- POST — neither safe nor idempotent (in the general case).
POST /orderstypically creates a new resource each time. Send it twice and you get two orders.
That last line is the whole problem with retries. GET, PUT, and DELETE can be retried freely. POST cannot — a blind retry risks a duplicate.
The idempotency key pattern
Some writes genuinely create something new and can’t be made idempotent by their nature — charging a card, placing an order, sending a message. You still want to retry them safely, because networks fail in the worst way: the server processes your request, then the response gets lost on the way back. The client thinks it failed; the work is actually done.
The fix is an idempotency key. The client generates a unique identifier (often a UUID) for the logical operation and sends it with the request, usually in a header:
POST /chargesIdempotency-Key: 9f8c2a1e-7b3d-4e6a-9c12-0f5a8b2d4e10
{ "amount": 4200, "currency": "usd" }The server records that key the first time it processes the request, along with the result. If a request arrives with a key it has already seen, the server skips the work and returns the stored result instead of doing it again. The client can now retry the same request as many times as it wants — same key, same outcome, exactly one charge.
This is how payment APIs handle retries; Stripe’s API, for instance, uses an Idempotency-Key header for exactly this reason. The pattern effectively makes a non-idempotent POST behave idempotently, scoped to that one key. A few details worth getting right: generate the key once per logical operation and reuse it across retries (a fresh key per attempt defeats the purpose), and have the server expire stored keys after some window so the table doesn’t grow forever.
FAQ
FAQ
Is POST ever idempotent?+
What's the difference between safe and idempotent?+
Why not just retry every failed request?+
Related reading
2026-06-04
ACID vs BASE: What Database Guarantees Actually Promise
ACID and BASE describe two ends of a tradeoff between strict correctness and scalable availability. Learn what each guarantee means, when each fits, and why most modern databases sit somewhere in between.
2026-06-04
Big-Endian vs Little-Endian
Byte order explained: how big-endian and little-endian lay out multi-byte numbers in memory, why network protocols pick one, and when the difference actually bites you.
2026-06-04
Big-O Notation in Plain English
Big-O describes how an algorithm's runtime or memory grows as input grows. Learn the common classes — O(1), O(log n), O(n), O(n log n), O(n^2), O(2^n) — with plain examples.
2026-06-04
CORS in Plain English: Why the Browser Blocks Your Fetch
A clear walkthrough of CORS and the same-origin policy — what an origin is, why your fetch fails, how servers opt in, and the big misconception about who CORS actually protects.
2026-06-04
Environment Variables and PATH, Explained
What environment variables actually are, why they hold config and secrets, and how PATH decides which binary runs when you type a command.
Get the best tools, weekly
One email every Friday. No spam, unsubscribe anytime.