Architecture patterns
Naturally idempotent operations
Some operations are safe to repeat by design, such as absolute sets, upserts and conditional writes. Learn to spot them, and to redesign the rest before you add keys.
An operation is idempotent when doing it twice leaves the system in the same state as doing it once. Many operations already are. Recognizing them saves you from building machinery you don't need.
Concept
The test for idempotency is about state, not responses. A second delete may return 404 instead of 200, and the operation is still idempotent, because the row is gone either way.
The big idea
Four patterns cover most of the idempotent operations you will write:
- Absolute sets.
SET status = 'shipped'orPUT /users/42 {"email": "a@b.com"}can run ten times and leave the same result.balance = balance + 10cannot. - Deletes. Deleting a row that is already gone changes nothing.
- Upserts on a natural key.
INSERT ... ON CONFLICT (order_id) DO NOTHINGturns "create" into "ensure exists". - Conditional writes. The update succeeds only if the version is what you last saw. DynamoDB's condition expressions, etcd's compare-and-swap and HTTP's
If-Matchheader all express this.
HTTP formalizes some of this. RFC 9110 defines GET, HEAD, PUT, DELETE, OPTIONS and TRACE as idempotent methods, which is why proxies may retry them. POST and PATCH are not, which is why "create a payment" needs help.
Walking through a conditional write
Compare two ways to add $10 to an account that has $130 at version 7:
-- Not idempotent: each replay adds another $10
UPDATE accounts SET balance = balance + 10 WHERE id = 42;
-- Idempotent: the first run matches version 7 and sets version 8
UPDATE accounts SET balance = 140, version = 8 WHERE id = 42 AND version = 7;Run the second statement twice. The retry finds version 8, matches zero rows, and changes nothing. The first statement would have added $20 in total.
Tip
Before you reach for idempotency keys, ask whether the operation can be redesigned. "Add $10 to the balance" becomes "record transaction txn_8f3a for $10", with a unique constraint on the transaction ID. A repeat hits the constraint and changes nothing. The payment system case study relies on this ledger approach.
Common mistakes
- Assuming
PUTis always safe. APUTthat sets a value computed from a read, such asbalance = old + 10, is not idempotent. The value must be given, not derived. - Equating identical responses with identical effects. Idempotency is about state. Good APIs also return the same response, but that is a bonus.
- Using
POSTfor everything. Where you can, give the operation a natural key so a repeat can be recognized.
Check yourself
1. Is SET balance = 140 idempotent?
Yes. Running it twice leaves the balance at 140.
2. Why is balance = balance + 10 not idempotent, and how do you fix it?
Each run adds another $10. Record the deposit as a transaction with a unique ID, so a repeat is rejected by the unique constraint.
Key takeaways
- Absolute sets, deletes, upserts and conditional writes are idempotent by construction.
- Relative updates such as "add 10" are not. Rewrite them as "set to this value" or as a transaction with an ID.
- Idempotency is about state, not about matching responses.
Further learning
Next, see how a client attaches a key to an operation that can't be redesigned, in idempotency keys.
Related
Idempotency keys
For operations that can't be made idempotent by design, a client-chosen key lets the server recognize a retry and replay the first answer. Learn the protocol and its failure cases.
Case study: payment system
Moving money correctly, with payment intents, idempotency at every hop, a double-entry ledger, webhooks and reconciliation when the network lies. Start here for the payments map.
Why duplicates happen
Retries at every layer and at-least-once queues mean the same request can arrive twice. Learn where duplicates come from, and why the receiver must tolerate them.