Idempotency in Distributed Systems
Retry (from two modules back) quietly assumed something this module finally makes explicit: that calling an operation twice is safe. This module is what makes that actually true.
Learning objectives
- Beginner: Define idempotency in one sentence and give an example of a naturally idempotent operation.
- Intermediate: Implement an idempotency key so a duplicate request doesn't double-process.
- Advanced: Explain why idempotency matters even MORE in an at-least-once message-delivery system like Kafka.
◆ The problem
A network call can fail in a way where the REQUEST succeeded on the server but the RESPONSE never made it back to the caller — from the caller's perspective, this is indistinguishable from the request never arriving at all, so a naive retry is a completely reasonable thing to attempt. If that operation was "charge this card $50," retrying it means charging the customer twice for one purchase.
An operation is idempotent if calling it once produces the exact same end result as calling it two, three, or a hundred times — setStatus(SHIPPED) is naturally idempotent (calling it repeatedly always leaves status as SHIPPED); incrementStock(-1) is NOT (calling it twice decrements twice).
Making an inherently non-idempotent operation (like "charge this card") SAFE to retry means the CALLER attaches a unique idempotency key to the request, and the SERVER remembers which keys it has already processed — a retried request with the same key returns the ALREADY-COMPLETED result instead of processing the charge a second time.
@PostMapping("/charge") public ChargeResult charge(@RequestHeader("Idempotency-Key") String key, @RequestBody ChargeRequest req) { Optional<ChargeResult> existing = idempotencyStore.get(key); if (existing.isPresent()) return existing.get(); // already processed — return the same result again ChargeResult result = paymentProcessor.charge(req); idempotencyStore.save(key, result); return result; }
💻 Code example
@PostMapping("/charge") public ChargeResult charge(@RequestHeader("Idempotency-Key") String key, @RequestBody ChargeRequest req) { Optional<ChargeResult> existing = idempotencyStore.get(key); if (existing.isPresent()) return existing.get(); ChargeResult result = paymentProcessor.charge(req); idempotencyStore.save(key, result); return result; }
◆ Under the hood
The Kafka & Microservices category already covers this from the messaging side: Kafka's SAFEST delivery guarantee without extra configuration is at-least-once, meaning a consumer can genuinely receive and process the SAME message more than once (after a rebalance, or a crash before committing an offset). A Kafka consumer writing to a database MUST be idempotent for exactly this reason — this module's pattern and that category's consumer-idempotency discussion are the same concept, viewed from the messaging side versus the HTTP side.
✓ Quick recap
- An operation is idempotent if repeating it produces the same end result as doing it once — not every operation naturally is.
- The idempotency-key pattern makes a non-idempotent operation safely retryable: the server remembers which keys it already processed and returns the same result again instead of reprocessing.
- Retry (this category) and at-least-once Kafka delivery (Kafka & Microservices category) both DEPEND on idempotency being deliberately designed in — it's not automatic.
Want a visual for this concept?
Generate a diagram tailored to “Idempotency in Distributed Systems” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →