Basicsbeginner10 min

Idempotency

Make an operation safe to repeat: running it twice leaves the system in the exact same state as running it once.

Imagine pressing the elevator call button. You press it once, then press it three more times because the car is slow — but the elevator still arrives exactly once. The extra presses change nothing. That property, where repeating an action gives the same result as doing it a single time, is called idempotency, and it turns out to be one of the most important ideas for building systems that behave correctly when things go wrong.

The problem

Out in the real world, the network is unreliable. A client sends a request, the server processes it, and then the response gets lost on the way back. The client never hears 'success', so it does the sensible thing and retries — and now the same request has arrived twice. The same thing happens with messaging systems that promise at-least-once delivery: to guarantee a message is never lost, they may deliver it more than once.

For a read this is harmless, but for an action that changes something it can be a disaster. Picture an operation like 'charge the customer's card $50'. Run it once and you bill them correctly; run it twice because of a retry and you've double-charged them. The duplicate wasn't a bug in your logic — it was a duplicate delivery — but the customer is angry all the same.

Step through it below. Ana buys a $50 coffee, the charge goes through, and the reply is lost. At that moment you decide what the app should do — then see what your choice costs, and switch to the other choice to compare.

How it works

An operation is idempotent if performing it any number of times has the same effect as performing it exactly once. The trick is in how the operation describes its change. Compare two ways to update a counter: x = 5 simply sets the value, so no matter how many times you run it, x ends up at 5. But x += 1 increments, so each run pushes the value higher — run it twice and you get 7 where you wanted 6.

A payment has no natural "set" form: "charge $50" is an increment by nature. So instead the client gives each operation a name. Before paying, the app generates a unique idempotency key and sends it with the request. The server keeps a key table: the first time it sees a key, it does the work and saves the result under that key. If the same key shows up again, it skips the work and replies with the saved result.

Replay Ana's payment below with a key attached. When the retry arrives, predict what the API does with it. Afterwards, flip the switch to No key to compare the two runs step by step.

Check yourself

A client retries a payment with the same idempotency key, and the original charge already succeeded. What should the server send back?

Note

Idempotent is not the same as 'no side effects'. An idempotent operation is allowed to do something — write to a database, send a confirmation — it just has to be safe to repeat. The promise is about the end state being identical, not about the operation doing nothing.

Idempotency in HTTP

The web's design leans on this idea, and the HTTP methods are a great cheat sheet. GET just reads data, so calling it repeatedly is naturally idempotent. PUT replaces a resource with a given value — like setting x = 5 — so two identical PUTs leave the resource in the same place. DELETE is idempotent too: deleting something that's already gone still leaves it gone.

POST is the odd one out. It usually means 'create a new thing', so each call creates another one — submit a form twice and you may get two orders. That's why POST is the method you most often have to protect against duplicates, which leads us to the practical tools.

Making operations idempotent

When an operation isn't naturally repeatable, you can add idempotency yourself. A few reliable techniques:

  • Idempotency keys (request IDs) — the client attaches a unique ID to the request. The server records which IDs it has already processed; if a duplicate arrives, it skips the work and returns the original result. This is exactly how payment APIs prevent double-charges on a retry.
  • Upserts — instead of a separate 'insert' that fails on the second try, use an 'insert-or-update' that converges to the same row no matter how many times it runs.
  • Prefer 'set' over 'add' — design the operation to declare the desired end state ('set the balance to 100') rather than a relative change ('add 100'). A 'set' is safe to repeat by construction.

Idempotency keys have a few details that are easy to get wrong. Save the key and the result in the same transaction as the work itself, so a crash can't leave a charge with no key (and a retry that charges again). Decide what happens when a retry arrives while the first attempt is still running: usually the second one waits, or gets a "try again shortly" reply, rather than starting the work twice. And if a request arrives with a known key but a different body — say $60 instead of $50 — reject it, because it's a bug in the client, not a retry.

Watch out

A key names one operation, not one customer. Generate a fresh key for every new payment and reuse it only for retries of that same payment. Build it from the customer ID or the amount and a genuine second purchase looks like a retry — and is silently skipped. Give saved results an expiry, too: long enough to outlast any retry (a day is a common choice), short enough that the table doesn't grow forever.

Tip

Idempotency is what makes retries safe. Without it, a retry is a gamble between losing the request and duplicating it. With it, you can retry whenever a reply goes missing and trust the result — which is precisely why it pairs so well with retry policies (and why a circuit breaker is still worth having, to stop those retries piling onto a service that's already down).

When it matters

Idempotency matters anywhere a request can be delivered or processed more than once — which, given unreliable networks and at-least-once messaging, is almost everywhere. It is essential for safe retries, for consuming events from a queue, and for any payment, booking, or order flow where a duplicate has real-world cost.

It also fits naturally with statelessness: when a server keeps no per-client memory between calls, retries can land on any instance, and idempotency is what guarantees they all reach the same end state. Master this one habit — design changes so repeating them is harmless — and a whole class of distributed-systems bugs simply disappears.

Check yourself

To make checkout idempotent, a developer uses the customer's ID as the idempotency key. What goes wrong?

Key takeaways

  • An operation is idempotent if performing it many times has the same effect as performing it once.
  • Networks retry and messages get delivered more than once, so any operation that can run twice eventually will — idempotency is what makes that safe.
  • A non-idempotent action like 'charge the card' double-charges on a retry; a 'set the value' action does not, because the second run changes nothing.
  • HTTP GET, PUT, and DELETE are designed to be idempotent; POST usually is not, because it creates something new each time.
  • Make operations idempotent with idempotency keys to dedupe retries, upserts, and 'set' semantics instead of 'add' semantics.

Keep going