Send a request over a network, and the request can fail in two different ways that look identical from where you're standing. Either it never arrived, or it arrived, was processed, and the response never made it back. A timeout looks the same either way. This is the single fact that a huge amount of distributed-systems design exists to work around, because the obvious fix — if you don't get a response, try again — is only safe in the first case. If a payment actually went through and the confirmation was lost in transit, retrying doesn't resend a request that failed; it sends a second, successful request for the same charge. The customer pays twice for one order, and nothing about the network layer did anything wrong. It behaved exactly as unreliable networks are specified to behave.
The word is borrowed from mathematics, where an operation is idempotent if applying it twice produces the same result as applying it once: f(f(x)) = f(x). Taking the absolute value of a number is idempotent — the absolute value of an absolute value is just the absolute value. Adding five is not — do it twice and you've added ten. Translated into an API: "set this account's balance to $500" is idempotent, because running it three times leaves the balance at exactly $500, the same as running it once. "Add $500 to this account's balance" is not, because running it three times adds $1,500. The distinction has nothing to do with how dangerous or important the operation is, and everything to do with a narrow structural question: does repeating the exact same request change the outcome versus running it once?
This is why HTTP's specification designates GET, PUT, and DELETE as idempotent and POST as not, and it's worth being clear about what that designation actually is: a promise about intended behavior, not a property the network enforces. Nothing stops a developer from writing a PUT handler that appends to a log instead of overwriting a record, which would violate the specification's assumption while remaining perfectly valid HTTP. Idempotency is a property of what a specific handler actually does when it runs twice, not a property conferred by which verb happens to be in the request line. The spec tells you what you're supposed to build. It can't build it for you.
Some operations are naturally idempotent because of what they mean, with no extra engineering required — "set status to shipped," "delete order 4471," "mark this record as read." Run any of these an arbitrary number of times and the world ends up in the same state as running it once, because the operation describes a destination rather than a delta. Whenever an operation can honestly be phrased this way, that's usually the cheapest fix available, and it's worth checking before reaching for anything more elaborate: a lot of "how do we make retries safe" problems dissolve the moment someone rephrases "increment the counter" as "set the counter to this specific value," if the caller has enough information to know what that value should be.
But plenty of real operations genuinely are deltas that can't be rephrased as destinations — "charge this card $40," "send this email," "deduct one unit of inventory." These need a different mechanism, and the standard one is the idempotency key. The client generates a unique identifier — typically a UUID — once, before making the first attempt, and attaches it to the request. The server, on receiving any request carrying that key, checks whether it has already completed a request with that exact key. If it has, it returns the previously recorded result without re-executing anything. If it hasn't, it performs the operation and records the key together with the result. A retry — same key, possibly sent because the client never saw the first response — lands on the "already completed" branch and comes back with the same answer, without the card being charged twice.
The mechanism above sounds simple, and the way it actually fails in practice is instructive: performing the operation and recording the key have to happen as a single atomic step, in the same transaction, or the key doesn't protect against anything. If a server charges the card and then, in a separate step, writes the idempotency key to a different table, there is a window between those two steps during which a retry arrives, finds no matching key yet, and charges the card a second time — the exact failure the key exists to prevent, now reintroduced by the gap between "do the thing" and "record that the thing was done." The fix has to be written so that both happen under one commit: either both the charge and the key record land together, or neither does. An idempotency key that's stored a moment after the side effect it's guarding, rather than atomically with it, is a placebo — it looks like protection and provides none during exactly the window it was built for.
It's worth being as precise here as anywhere else on this site, because idempotency has a specific job and it's easy to credit it with a bigger one. An idempotency key answers one question: has this exact logical request, identified by this exact key, already been carried out? It says nothing about ordering — two different requests with two different keys can still arrive and be processed out of sequence, and nothing about idempotency prevents that, because they're not retries of each other, they're genuinely different operations that happen to race. It doesn't help if the client itself is confused about which requests are logically "the same" — generate a new key for what should have been a retry of an existing request, and the server correctly, faithfully, uselessly treats it as new. And it doesn't make a whole multi-step process atomic: an operation that charges a card in one system and sends a confirmation email in another can still charge exactly once while emailing twice, if the email step isn't independently made idempotent, because the idempotency key on the charge has no reach into a system it was never attached to.
There's a related and equally precise distinction worth naming: "at least once" delivery combined with idempotent processing produces an outcome that looks like "exactly once" from the outside, but that's a simulated property, not a delivered one. Message queues and network protocols that guarantee at-least-once delivery do so by retrying anything they're not sure got through, which means duplicates are an expected, routine occurrence at the transport layer — not a rare edge case. What makes the overall system behave as though each message were processed exactly once is the idempotent handler underneath absorbing those duplicates silently. True exactly-once delivery, in the sense of a network that guarantees a message arrives once and only once with no cooperation from the receiver, isn't something you can build over an unreliable network — the client still can't tell, from the network alone, whether its first attempt got through before it decides to retry. What idempotency buys is not knowledge of what happened. It's permission not to need that knowledge, because the answer stays the same no matter how many times the question gets asked.