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/123 may return 204 the first time and 404 the 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:

  1. Each Saga step must be retry-safe, because the orchestrator cannot know whether a step that timed out actually ran.
  2. 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.

Several patterns in the vault are idempotency wearing other clothes:

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.