An operation is idempotent if doing it once or doing it many times has the same effect. "Set the balance to 100" is idempotent; "add 100 to the balance" is not. In distributed systems, retries and duplicate deliveries are normal, so any operation that might be repeated must be made idempotent, or users get charged twice.
In simple words
Pressing a lift button five times doesn't call five lifts; it's still one request. An idempotent operation is like that: doing it again by accident changes nothing. That's exactly what you want for "pay ₹500" when the network makes the app retry.
A retry with the same key K1 gets the saved result back instead of charging the card again.
Why It Matters
client --POST /pay--> server (charges card, commits)
client <---- response lost (timeout) ----
client: "did it work? retry!" --POST /pay--> charged AGAIN
The client cannot tell "the request failed" from "the request succeeded but the response was lost". Queues deliver at-least-once, so consumers see duplicates too. The safe answer is to retry, and to make the retry harmless.
How It Works
HTTP semantics: GET, PUT and DELETE are defined as idempotent; POST is not. "PUT /users/42 {name}" can be retried freely.
Idempotency keys: the client generates a unique key per logical operation and sends it with every retry. The server stores key → result; a repeat returns the stored result instead of redoing the work.
Natural keys and unique constraints: a unique index on (order_id) in the payments table makes a second insert fail harmlessly.
Conditional writes: "update only if version = 7" turns a replayed write into a no-op.
POST /v1/payments
Idempotency-Key: 5f1c-...-9a2e (client-generated, reused on retry)
server:
BEGIN
INSERT INTO idempotency(key, status) VALUES ($key, 'processing')
-- unique key: a duplicate insert fails -> return the stored response
charge card ...; store response under key
COMMIT
Key Pointers
Assume every request and message can arrive more than once.
Store the idempotency record in the same transaction as the side effect.
Keys need a scope (per user/merchant) and an expiry (e.g. 24 hours).
Follow-up Questions
Two retries with the same key arrive at the same time. What happens?
The unique constraint (or an atomic set-if-absent) lets only one proceed; the other either waits and returns the stored result, or gets a "request in progress" (409) and retries later.
Can a message consumer be idempotent without keys from the client?
Yes: use the message id (or a business id such as order id) and record processed ids, or make the operation naturally idempotent (upsert by id, set state rather than increment).