Phase 0

Idempotency: why it matters more than you think

What it means for an action to be safe to repeat, and why every retry, payment, and webhook depends on it.

#what is idempotency#idempotent api explained#why is idempotency important
Idempotency: why it matters more than you think - diagram

The elevator button test

Press an elevator call button once. Press it five more times because you're impatient. The elevator still only comes once - pressing it again after the first press changes nothing. That's idempotency: doing the same operation multiple times produces the same result as doing it once.

Compare that to ordering a coffee. Say "one coffee" five times to five different baristas and you get five coffees. Not idempotent - every repetition adds a new effect.

This distinction sounds almost too simple to matter, until you remember that networks fail constantly, in ways that make "did that actually happen?" a genuinely hard question to answer from the client's side.

Why this is a real engineering problem, not a theoretical one

Send a payment request. The server processes it successfully and charges the card - but the response gets lost on the way back, maybe the connection drops, maybe a proxy times out. From the client's point of view, nothing came back. Did it fail? Did it succeed? There's no way to tell from where you're standing.

The honest answer is: you can't know, so the only safe thing to do is retry. And now the real question is what happens if that retry hits a server that already processed the first attempt. If the operation isn't idempotent, the user gets charged twice for something that looked, from their end, like it might have just failed once.

Interactive example

A retry that doesn't charge twice

Step through a payment whose response gets lost on the way back.

Step 1 of 6

Client sends payment

The client sends POST /payments with an Idempotency-Key header it generated - a unique value identifying this particular attempt.

Which HTTP methods are idempotent by convention

  • GET, PUT, DELETE are supposed to be idempotent. Calling DELETE /orders/42 five times should leave the system in the same state as calling it once - the order is gone, and it stays gone.
  • POST is not idempotent by convention, because it usually means "create a new thing." Five identical POST requests to /orders conventionally create five orders.

This is a convention, not something HTTP enforces - nothing stops a badly written DELETE handler from doing something different every time it's called. But clients, cachesCacheA copy of data kept somewhere faster to read from than its original source, so repeated requests don't have to pay the full cost every time. Deliberately allowed to be wrong or empty - a cache miss should never be treated as an error., and retry logic all assume the convention holds, which is exactly why breaking it causes hard-to-trace bugs elsewhere in the system.

How idempotency keys actually work

For operations that are inherently creation-like - placing an order, charging a card - you can't rely on the HTTP method alone, so a common pattern is the client generating a unique idempotency key and attaching it to the request:

text
POST /payments
Idempotency-Key: 7e5f3c2a-9b41-4d8e-a1f0-3c6d9e2b7a44

{ "amount": 4999, "currency": "usd" }

The server stores that key alongside the result of the first successful request. If the exact same key shows up again - because the client retried after a timeout - the server returns the original result instead of processing the payment a second time. The client can retry as aggressively as it wants; the operation itself only ever actually happens once.

Where this shows up constantly

Payment APIsAPIA defined way for one piece of code to ask another to do something, without needing to know how it happens internally. Not a specific technology - a function signature, a library's exports, and a REST endpoint are all APIs. (Stripe's idempotency keys are the textbook example), webhook consumers (a webhook provider will redeliver an event if your endpoint doesn't acknowledge it fast enough, so your handler needs to tolerate receiving the same event twice), and message queueMessage queueA component that lets one part of a system hand off work to another without waiting for it to finish immediately - the sender moves on right away, and a separate worker processes the job whenever it gets to it. consumers (a message can be redelivered after a consumer crashes mid-processing, before it acknowledges completion) all lean on this same idea. None of them can guarantee exactly-once delivery over an unreliable network - so instead, they make the operation itself safe to receive more than once.

Interview prep

This topic comes up in interviews - 3 questions, leveled by role.

See the questions →