On this page

Idempotency

An operation is idempotent if doing it once or many times gives the same result.

1Set balance = 500 → run 3 times → balance = 500 ✔ idempotent
2Add 500 to balance → run 3 times → balance + 1500 ✘ not idempotent

Think of a lift button: pressing it 5 times doesn't call 5 lifts. One press or many, the result is the same.

Why It Matters

Networks fail. A client often can't tell if a request failed or just the response got lost.

Ravi gets charged twice. Retries are necessary for reliability, so the server must make them safe. That's what idempotency does.

HTTP Methods

MethodIdempotent?Why
GET✔Only reads
PUT✔Replaces with the same value every time
DELETE✔Once deleted, it stays deleted
POST✘Usually creates something new each time
PATCHNot guaranteed"set name=Ravi" is, "increment count" isn't

Idempotency is about the server's state, not the response. A second DELETE may return 404, but the state is still the same.

Safe vs idempotent: safe means "changes nothing" (GET). Idempotent means "repeating changes nothing extra" (PUT, DELETE).

Idempotency Key (The Main Technique)

The client generates a unique ID per operation and sends it with the request. The server remembers keys it has already processed.

1POST /payments
2Idempotency-Key: 7f3c-91ab-...
3{ "amount": 500 }
1Server receives request with key 7f3c
2 → Key seen before?
3 YES → return the saved response (don't charge again)
4 NO → process payment → save (key → response) → return response

Same retry, same key, one charge. This is how Stripe and most payment APIs work.

Important Details

CaseWhat to do
Same key, request still processingReturn 409 Conflict or make the client wait; don't run it twice
Same key, different bodyReject with 422: the key was reused by mistake
How long to keep keysUse a TTL, e.g. 24 hours
Where to storeRedis (fast) or DB table with a unique constraint on the key
Key generated byThe client (a UUID), created once per operation, reused on every retry

Race Condition

What if two retries arrive at the exact same moment?

1Time Request A (key 7f3c) Request B (key 7f3c)
2t1 Is key 7f3c saved? → No
3t2 Is key 7f3c saved? → No
4t3 Charge ₹500 ✔
5t4 Charge ₹500 ✔ ← double charge ✘

Both checked before either one saved the key, so both thought they were first.

Think of two people trying to book the last seat in a cinema at the same time. If the system only "looks" at the seat first and "books" it later, both may see it as empty. The fix is to look and book in one single step.

That single, uninterruptible step is called an atomic operation. Two common ways:

WhereHowResult
RedisSET 7f3c "IN_PROGRESS" NXNX = "set only if it doesn't exist". First request succeeds, second gets "already exists"
DatabaseINSERT key with a unique constraintFirst insert succeeds, second fails with a duplicate error
1Time Request A Request B
2t1 Claim key 7f3c → success ✔
3t2 Claim key 7f3c → already taken ✘
4t3 Charge ₹500 ✔ Return 409, or the saved result

Now only one request does the work.

Crash Safety

Another problem: the server crashes between two steps.

1Case 1: Charge ₹500 ✔ → crash → key never saved
2 Retry sees no key → charges again ✘
3
4Case 2: Save key ✔ → crash → payment never done
5 Retry sees key → skips → Ravi never paid ✘

Fix: save the key and do the work in the same database transaction. A transaction is all-or-nothing: either both happen, or neither does.

1BEGIN
2 insert idempotency key 7f3c
3 create payment record ₹500
4COMMIT → both saved together, or nothing saved on crash

If the work calls an outside system (like a payment gateway) that can't join your transaction, pass the same idempotency key to that system too, so it also ignores duplicates.

Other Ways to Get Idempotency

TechniqueExample
Unique constraintOne order per (userId, cartId)
UpsertINSERT ... ON CONFLICT DO NOTHING
Set instead of increment"status = PAID" instead of "count = count + 1"
Conditional updateUPDATE orders SET status='PAID' WHERE id=501 AND status='PENDING'
Version checkUpdate only if version = 7 (optimistic locking)

Idempotency in Message Queues

Most queues (Kafka, SQS) deliver at least once, so a consumer can get the same message twice.

1Consumer gets "OrderPlaced #501"
2 → already in processed_messages table? → skip
3 → else → send email → insert message ID into processed_messages

Consumers must be idempotent, or users get duplicate emails, duplicate deliveries, or double refunds.

Summary

1Idempotent = same result whether run once or many times
2→ needed because retries are unavoidable
3→ GET, PUT, DELETE are idempotent; POST is not
4→ make POST safe with an Idempotency-Key + stored response
5→ use atomic checks (Redis NX / unique constraint) for races
6→ queue consumers must dedupe by message ID

Next, we'll look at Rate Limiting, which controls how many requests a client can send in a given time.

Next TopicRate Limiting