Summary: An operation is idempotent when performing it several times has the same intended effect as performing it once. That single sentence sits underneath a surprising number of the vault's arguments: which HTTP method to choose, whether an action endpoint is safe, how a Saga step recovers, what a rate limiter does when its datastore blinks, and why a retry can amplify an outage instead of healing it. This page collects the idea in one place, because it is currently explained in whichever article a reader happens to open first.
Idempotency is not a property a system acquires by being careful. It is either designed in or absent, and its absence is invisible until the network fails — which is to say, until production.
The definition, precisely
Repeating the request leaves the same intended end state. Two things that definition deliberately does not require:
- It is not "the response is identical."
DELETE /users/123may return204the first time and404the second. The resource is absent either way, so the operation is idempotent; the status code is not the test. - It is not "nothing happens on the retry." Side effects may legitimately occur — a log line, an audit record. What must not change is the state the caller was asking for.
The related-but-distinct property is safety: a safe method does not modify state at all. GET and HEAD are safe, and safe implies idempotent. The reverse does not hold — DELETE is idempotent but certainly not safe.
HTTP method semantics
| Method | Safe | Idempotent | Why |
|---|---|---|---|
GET, HEAD |
yes | yes | no state change |
PUT |
no | yes | the client supplies the desired representation; resending it converges to the same state |
DELETE |
no | yes | once absent, absent again |
POST |
no | no | the server decides what processing means; repeating may create or execute twice |
PATCH |
no | not inherently | depends entirely on the patch format |
The PATCH row is the one that catches people. A JSON Merge Patch setting {"status": "cancelled"} is naturally idempotent — applying it twice gives the same result. A JSON Patch operation like {"op": "add", "path": "/items/-", "value": …} appends, so applying it twice appends twice. Same method, opposite property, decided by the wire format the API chose. Why Modern APIs Don't Always Use PUT and DELETE works through when each method's semantics actually fit the operation.
Idempotency keys: making POST safe to retry
POST is where real business operations live — POST /orders/123/cancel, POST /payments/456/capture — and POST is exactly the method with no idempotency guarantee. The client sends the request, the connection drops, and the client cannot distinguish "never arrived" from "succeeded but the response was lost." If the operation triggers a refund, a notification and an inventory movement, guessing is expensive in both directions.
The standard answer is a client-generated idempotency key:
POST /payments/456/capture
Idempotency-Key: 7f2c9a1e-...
The server records the key with the outcome of the first execution. A later request bearing the same key returns that stored outcome instead of executing again. Stripe popularized this, and The API Design Patterns Nobody Teaches You treats it as a first-class API pattern rather than an implementation detail.
Getting it right has sharp edges that are easy to miss:
- The key must be generated by the client, before the first attempt — and reused across retries. A key minted per attempt provides nothing.
- The record must be written in the same transaction as the effect. Otherwise a crash between "do the work" and "remember that we did it" produces exactly the double-execution the key exists to prevent.
- Keys need a retention window, long enough to outlive any realistic client retry and short enough to bound storage.
- The same key with a different payload is a client bug, and should be rejected rather than quietly served the old result.
Choosing POST is easy; designing a POST operation that survives retries is the hard part.
At-least-once delivery makes it mandatory
In messaging, idempotency stops being an API nicety and becomes a correctness requirement. Practical message infrastructure offers at-least-once delivery: a message may be delivered more than once, because the alternative — exactly-once — is not achievable end-to-end without the consumer participating.
This is why the consumer's handler must be idempotent. The broker cannot provide the guarantee alone; it can only guarantee that the message arrives, not that it arrives once. Async APIs - don't confuse your events, commands and state draws the distinction that makes this tractable: a command ("cancel this order") carries different replay risk from an event ("this order was cancelled"), and conflating them loses the information needed to reason about duplicates.
Distributed transactions and compensation
What Senior Architects Know About SAGA That Juniors Don't depends on idempotency twice over, and both dependencies are easy to overlook:
- Each Saga step must be retry-safe, because the orchestrator cannot know whether a step that timed out actually ran.
- Each compensating action must be idempotent too. Compensation runs during failure handling — the least reliable moment in the system's life — so "refund this payment" must not refund twice when the compensation itself is retried.
A Saga built from non-idempotent steps does not fail loudly. It fails by double-charging a customer during an incident, which is the worst available time to discover the design flaw. This is also why Database Concepts Every Backend Developer Must Understand matters here: the ACID transaction that would have made the problem disappear is precisely what a distributed operation cannot have.
Where retries become the outage
The property has an operational face. A retry policy layered on a non-idempotent operation is not merely incorrect — it is an amplifier. Dissecting the AWS Outage A Step-by-Step Breakdown of What Went Wrong shows the general shape: a degraded dependency causes clients to retry, retries multiply load on the thing that was already struggling, and the system converges on collapse rather than recovery. The mitigations that keep retries survivable — exponential backoff, jitter, circuit breakers, retry budgets — all assume the underlying operation is safe to repeat. Without that assumption they are just a more polite way to corrupt data.
The Senior Review Bottleneck supplies the modern version of how this gets shipped: an AI-generated retry policy that is locally reasonable and globally wrong, because "the retry logic would turn a temporary dependency failure into a traffic amplifier" is a system-level insight that does not appear anywhere in the diff being reviewed.
Related mechanisms
Several patterns in the vault are idempotency wearing other clothes:
- Token buckets and counters. Designing a Distributed API Rate Limiter wraps its read-modify-write in a Lua script precisely so a concurrent repeat cannot consume the same token twice — atomicity in service of a correct-once update.
- Convergent infrastructure. Declarative deployment is idempotent by construction: apply the manifest as often as you like, the cluster converges to the same state. That is the same
PUTreasoning applied to infrastructure, and it is why CNCF Working Group Sets Some Standards for 'GitOps' can treat a git commit as the desired state rather than a change instruction. - Migration bundles. Introducing DevOps-friendly EF Core Migration Bundles relies on a migrations table so that re-running a deployment does not re-apply a schema change — the database's own idempotency key.
- Serve-stale and overlays. Designing a URL Shortener That Works Like Infrastructure treats repeated writes to the control plane as convergent, which is what lets the read path fail safe.
The practical test
Before shipping an endpoint or a handler, ask the question the network will eventually ask for you:
If this runs twice, what does the customer see?
If the answer is "the same thing," it is idempotent. If the answer is "two charges," "two emails," or "a duplicate row," then either the semantics must change or a key must be introduced — and deciding that after the first incident is considerably more expensive than deciding it now.
Related
- Why Modern APIs Don't Always Use PUT and DELETE — method semantics in practice, and why action endpoints inherit POST's lack of a guarantee.
- The API Design Patterns Nobody Teaches You — idempotency keys as a first-class API pattern, alongside versioning and error contracts.
- What Senior Architects Know About SAGA That Juniors Don't — retry-safe steps and idempotent compensation as the foundation of distributed transactions.
- Async APIs - don't confuse your events, commands and state — at-least-once delivery, and why command/event typing decides replay risk.
- Designing a Distributed API Rate Limiter — atomic counter updates as correct-once semantics on shared state.
- Dissecting the AWS Outage A Step-by-Step Breakdown of What Went Wrong — retry amplification when the underlying operation is not safe to repeat.
- Database Concepts Every Backend Developer Must Understand — the ACID guarantees a distributed operation gives up, making idempotency the replacement.
- Best Practices for Better RESTful API — the method conventions this property underpins.
- Designing a URL Shortener That Works Like Infrastructure — convergent writes at infrastructure scale.
- Introducing DevOps-friendly EF Core Migration Bundles — re-runnable deployments via a migrations ledger.
- CNCF Working Group Sets Some Standards for 'GitOps' — declarative convergence as idempotency applied to infrastructure.
- The Senior Review Bottleneck — why a plausible-looking retry policy is exactly the kind of defect review now has to catch.