← Software Engineering & Cloud Studio
Concept Explainer · Software Engineering

Idempotent vs. Non-Idempotent Operations

Why retrying a request after a network timeout is completely harmless against some endpoints and genuinely dangerous against others — and it has nothing to do with whether the endpoint is "read-only."

Ask what "idempotent" means and you'll usually hear "safe" or "read-only" — neither is right, and the imprecision matters because idempotency is the exact property that decides whether an automatic retry is harmless or dangerous. An operation is idempotent when calling it once and calling it ten times land you in the exact same end state— not when it does nothing, and not when it merely "seems safe." "Set the account balance to $100" is idempotent: run it any number of times and the balance is $100. "Add $100 to the account balance" is not: run it twice and the balance has gone up by $200. That single distinction is why some retries are trivially safe and others can double-charge a customer or create duplicate orders.

The Setup

Two kinds of retry, two very different outcomes

Timeouts are inherently ambiguous. When a client's request times out waiting for a response, the client cannot tell whether the server never received it, received it but crashed mid-processing, or actually finished the work and the response was simply lost on the way back — a dropped connection, a load-balancer failover, a mobile network hiccup. The only universally sane reaction to that ambiguity is to retry the request. But whether that retry is safedepends entirely on what happens if the original request actually did succeed the first time. If the underlying operation is idempotent, a duplicate call is harmless — it just re-confirms a state that's already correct. If it isn't, a duplicate call performs the underlying side effect a second time.

Same client, same retry, two different endings

✓ IDEMPOTENT — PUTPUT /accounts/42 { balance: 100 }server: sets balance = $100✕ response lost — timeoutClient retries: PUT balance: 100— identical request, sent againserver: sets balance = $100 (again)balance = $100✓ same final state either way✕ NON-IDEMPOTENT — POSTPOST /charges { amount: 100 }server: creates charge #A184, $100✕ response lost — timeoutClient retries: POST amount: 100— identical request, sent againserver: creates NEW charge #A185, $100TWO charges = $200✕ wrong — customer double-billed
Idempotent
f(f(x)) = f(x)
Retrying is always safe. Worst case, a duplicate call just re-applies the same end state that already held.
Non-idempotent
f(f(x)) ≠ f(x)
Retrying without protection repeats the side effect — a second charge, a second order, a second email.

None of this means non-idempotent operations are unfixable, or that you should never let a client retry a POST. It means the safety has to be built in deliberately rather than assumed for free — most commonly by having the client attach a unique idempotency key to the request and having the server deduplicate on it, turning an inherently non-idempotent action into one that behaves idempotently across retries.

Making a POST safe to retry: idempotency keys

POST /chargesIdempotency-Key: abc123request #1key not found →process + store resultchargecreated ✓DEDUP STOREabc123 → charge #A184(stored after request #1)POST /charges (retry)Idempotency-Key: abc123same keykey found →skip processing, return cachesame chargereturned ✓
Without a key
Every retry = new side effect
The server has no way to recognize a retry as "the same request I already handled."
With a key
Every retry = same cached result
A non-idempotent action, by convention, now behaves idempotently across retries.
Why this works

Idempotency is a promise about the end state — not a promise that the server does nothing.

The server can still do real work on every single call — recomputing a value, overwriting a row, re-checking a condition — and the operation is still idempotent as long as that work converges on the same result every time. That's the mathematical shape of it: f(f(x)) = f(x), applying the function to its own output changes nothing further. This is also why so much of distributed-systems infrastructure — TCP retransmission, load-balancer failover, message queues that guarantee "at-least-once" delivery, mobile clients that retry on flaky networks — quietly assumes the operations sitting behind it are idempotent. Nobody asks permission before retrying; the network layer just does it whenever it's unsure whether the first attempt landed. Designing an operation to be idempotent in the first place — by keying on a stable identifier and a target value rather than a relative delta — is almost always cheaper than bolting on deduplication after the fact.

Common misconception
"Idempotent just means read-only, or safe."

False — and HTTP's own spec is proof, because it defines "safe" (no side effects, purely retrieval) and "idempotent" (same end state no matter how many times it's repeated) as two separate properties, not synonyms. DELETE is idempotent even though it is clearly not read-only: deleting a resource writes to the system's state, but calling DELETE on the same resource five times in a row leaves it in exactly the same end state as calling it once — gone. The first call removes it; every call after that is a no-op against a resource that's already absent. PUT is idempotent for the same reason, despite writing data on every call: replacing a resource with the same representation twice leaves that resource in the same state both times. POST is the one that's typically NOT idempotent, precisely because its conventional job is to create a new resource on every call — which is a write, same as PUT and DELETE, just one whose repeated effect doesn't converge.

GET
✓ idempotent
read-only / safe
PUT
✓ idempotent
writes data
DELETE
✓ idempotent
writes data
POST
✕ not idempotent
writes data
Related Concept Explainers
Race Conditions vs. Deadlocks
Read it →
At-Least-Once vs. Exactly-Once Delivery
Coming soon

Idempotent vs. Non-Idempotent Operations — Concept Explainer

Explains what idempotency actually means — an operation whose repeated application always converges on the same end state — versus non-idempotent operations whose effect compounds with every call, and why that distinction, not 'read-only' or 'safe,' is what determines whether an automatic retry after a network timeout is harmless or dangerous.

Why This Is Commonly Misunderstood

Idempotent is often used loosely as a synonym for "safe" or "has no side effects," but HTTP itself treats safety and idempotency as two separate properties. GET is both safe and idempotent. PUT and DELETE are idempotent but not safe — they write or remove data every time they run. POST is typically neither, because its conventional purpose is to create a new resource on each call. Confusing the two leads engineers to assume any endpoint that "writes" must be unsafe to retry, when the real question is whether repeating the write converges on the same end state.

The Mechanics

Formally, an operation f is idempotent if f(f(x)) = f(x): applying it to its own result changes nothing further. "Set balance to $100" satisfies this — the balance is $100 after one call or a hundred. "Add $100 to balance" does not — each call changes the result of the last one. Network timeouts make this distinction operationally critical: a client that times out cannot tell whether its request succeeded server-side or not, so the only general-purpose recovery is to retry — and that retry is unconditionally safe only when the underlying operation is idempotent.

Where This Matters In Practice

Load balancers failing over, TCP retransmission, mobile clients on flaky networks, and message queues offering "at-least-once" delivery all retry silently and by default, which means every operation sitting behind them inherits an implicit expectation of idempotency. When an operation cannot be made naturally idempotent — most commonly a POST that creates a new record, like a payment or an order — the standard fix is an idempotency key: the client generates a unique key per logical request, the server deduplicates on it, and a retried request with the same key returns the original result instead of repeating the side effect.

Frequently asked questions

Is DELETE really idempotent if the resource is already gone the second time?

Yes — that's exactly the point. The first DELETE call removes the resource; every subsequent call against the same resource leaves it in the identical end state (absent), even if the server returns a 404 instead of a 200 on the second call. The HTTP-level idempotency guarantee is about the resource's state, not about the response code being identical every time.

Why is POST typically not idempotent?

By REST convention, POST is used to create a new subordinate resource — submit this order, create this comment, charge this card. Each successful call is designed to produce a new result (a new row, a new ID), so calling it twice produces two results instead of one. This is a design convention, not a law of physics: an endpoint could be built to accept POST and still behave idempotently, but you should not assume it does unless the API documents that guarantee.

Is PATCH idempotent?

It depends on how the patch is expressed, which is why HTTP does not classify PATCH as idempotent by default the way it does PUT. A PATCH that sets a field to an absolute value ("set status to shipped") is idempotent. A PATCH that expresses a relative change ("increment quantity by 1" or JSON Patch's "add to array") is not, because repeating it keeps changing the result.

Does making an operation idempotent mean the server does no extra work on a retry?

Not necessarily. A naturally idempotent operation like PUT may still fully re-execute its logic on every call — it just happens to converge on the same result. An idempotency-key-based fix typically does the opposite: it deliberately skips re-execution on a detected retry and returns a cached result, which is a performance optimization on top of idempotency, not a requirement of it.

🎓

Try our Software Engineering & Cloud Studio

More calculators, simulators, and guides for this discipline.

Related tools & guides

REST API Design Best PracticesAPI DesignerSoftware Engineering & Cloud System ArchitectureSoftware Engineering Reference