What Is Idempotency? Idempotent APIs Explained
An idempotent operation produces the same result no matter how many times it runs. Why that matters for retries, payments, and reliable APIs.
An operation is idempotent if running it once has the same effect as running it many times. Call it twice, ten times, or a hundred times with the same input, and the system ends up in the same state as if you’d called it just once. It’s a simple property with a huge practical payoff: it makes retries safe.
Why retries need it
Networks fail in the worst possible way — silently, and in the middle of a request. If a client sends a request to charge a credit card and the connection drops before the response arrives, the client has no idea whether the charge went through. Did the server never receive it? Did it process it and the response got lost? The only safe options are to give up (bad for reliability) or retry (bad if the original request actually succeeded, because now the card gets charged twice).
Idempotency breaks that dilemma. If “charge the card” is idempotent, the client can retry freely — the second attempt either does nothing (because the first one already succeeded) or completes the original operation, but the customer is never charged twice. This is the same failure mode that message queues and webhook delivery systems are built around: see what a message queue is and what a webhook is for how “at-least-once delivery” pushes the idempotency problem onto whoever receives the message.
HTTP methods and idempotency
The HTTP specification actually assigns idempotency guarantees to methods, though not all servers honor them correctly:
| Method | Idempotent? | Why |
|---|---|---|
GET | Yes | Read-only, no state change |
PUT | Yes | Replaces a resource with a given representation — repeating it sets the same final state |
DELETE | Yes | Deleting an already-deleted resource still leaves it deleted |
PATCH | Not guaranteed | Depends on whether the patch is a full replace or a relative change like “increment by 1” |
POST | No | Conventionally used to create a new resource or trigger a side effect each time |
PUT /users/42 {"name": "Ava"} is idempotent because the end state — user 42 has name “Ava” — is the same whether you send it once or five times. Compare that to POST /orders, which conventionally creates a new order every time it’s called, even with identical input. That’s the method REST APIs generally use for creation, which is exactly why it’s the one that needs an extra mechanism to make retries safe — see what a REST API is for the broader method conventions this builds on.
Idempotency keys
Since POST isn’t idempotent by default, APIs that need retry-safe creation — payments, order placement, anything with a real-world side effect — commonly add an idempotency key: a client-generated unique identifier attached to the request.
The flow looks like this:
- The client generates a unique key (typically a UUID) before making the request.
- The client sends the key in a header, such as
Idempotency-Key: 8f14e45f-.... - The server checks whether it has already processed a request with that key. If not, it performs the operation and stores the key alongside the result.
- If a request with the same key arrives again — because the client retried after a timeout — the server returns the stored result from the first attempt instead of repeating the side effect.
This turns a non-idempotent operation into an effectively idempotent one from the client’s perspective, without changing what the operation actually does. The key is scoped to a single logical operation; a new purchase gets a new key, but retries of that same purchase reuse it.
Idempotency vs safety
These two terms get conflated, but they’re distinct. A safe method has no side effects at all — GET is safe because it doesn’t change server state. An idempotent method can have side effects, but repeating it doesn’t compound them. DELETE is idempotent but not safe: it does change state on the first call, it just doesn’t change it further on repeats.
This distinction matters when you’re designing race-condition-prone endpoints. An idempotency key prevents duplicate side effects from retries, but it doesn’t by itself prevent two genuinely concurrent requests from interleaving badly — that’s a separate problem, covered in what a race condition is. Idempotency keys are usually implemented with a unique constraint or lock on the key in the database precisely to close that gap.
Designing for it
A few practical patterns show up repeatedly in idempotent API design:
- Use client-generated IDs for creation. Instead of letting the server assign a resource ID on
POST, have the client generate a UUID and usePUT /resources/{id}to create it. This makes creation naturally idempotent — retrying the samePUTjust re-sets the same resource. - Store idempotency keys with a TTL. Keep processed keys around long enough to cover realistic retry windows (minutes to hours), then expire them — keeping every key forever isn’t necessary and adds unbounded storage growth.
- Return the original response, not a new one, on a duplicate key. The client shouldn’t be able to tell whether its retry hit the original execution or a cached result — the response should be identical.
- Scope keys per operation type. A key used for “create order” shouldn’t collide with one used for “cancel order,” even from the same client.
The takeaway
Idempotency means repeating an operation doesn’t change the outcome beyond the first successful attempt. GET, PUT, and DELETE get this for free from their semantics; POST and side-effect-heavy operations need an explicit mechanism — typically a client-supplied idempotency key the server deduplicates against. Building this in up front is what makes network retries, at-least-once message delivery, and flaky mobile connections safe to design around instead of something to work around.
Keep reading
Takina · · 4 min read The Fetch API Explained: Making HTTP Requests in JavaScript
The Fetch API is JavaScript's built-in interface for making HTTP requests. How it works, its promise-based flow, and where it trips people up.
Takina · · 4 min read TypeScript Abstract Classes, Explained
Abstract classes in TypeScript define shared implementation plus methods subclasses must fill in. How they differ from interfaces and when to reach for them.
Takina · · 4 min read What Is the Beacon API? navigator.sendBeacon()
The Beacon API lets a page send one last async request as it unloads, without blocking navigation or racing the browser's page teardown.