Guide

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' or PUT /users/42 {"email": "a@b.com"} can run ten times and leave the same result. balance = balance + 10 cannot.
  • Deletes. Deleting a row that is already gone changes nothing.
  • Upserts on a natural key. INSERT ... ON CONFLICT (order_id) DO NOTHING turns "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-Match header 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:

SQL
-- 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 PUT is always safe. A PUT that sets a value computed from a read, such as balance = 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 POST for 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.