distributed systems25 min

Idempotency

Designing operations that are safe to retry without side effects

0/9Not Started

Why This Matters

Networks are unreliable. A request might succeed but the response gets lost, so the client retries. Without idempotency, that retry could charge a customer twice, send two emails, or create duplicate records. In distributed systems, you must assume every request might be delivered more than once.

Idempotency is the property that performing an operation multiple times produces the same result as performing it once. It is the foundation of reliable communication in any system where retries, timeouts, or message duplication can occur.

Define Terms

Visual Model

ClientSends key: abc-123
ServerProcesses request
Key Storeabc-123 -> result
First ResultCreated: charge $50
Duplicate DetectedKey already exists
Cached ResultReturn same response
1. Request + key
2. Store result
3. First response
4. Retry (same key)
5. Check key
6. Found!
7. Same result

The full process at a glance. Click Start tour to walk through each step.

Idempotency keys let servers detect and safely handle duplicate requests.

Code Example

Code
// Idempotent API with idempotency keys

class PaymentService {
  constructor() {
    this.processedKeys = new Map(); // key -> result
    this.balance = 1000;
  }

  charge(amount, idempotencyKey) {
    // Check if we already processed this key
    if (this.processedKeys.has(idempotencyKey)) {
      console.log(`Duplicate request detected (key: ${idempotencyKey})`);
      return this.processedKeys.get(idempotencyKey);
    }

    // Process the charge
    this.balance -= amount;
    const result = {
      status: "success",
      charged: amount,
      remaining: this.balance
    };

    // Store result for future duplicate detection
    this.processedKeys.set(idempotencyKey, result);
    console.log(`Charged $${amount} (key: ${idempotencyKey})`);
    return result;
  }
}

const payments = new PaymentService();
const key = "pay_abc123";

// First request - processes normally
console.log(payments.charge(50, key));
// Retry with same key - returns cached result
console.log(payments.charge(50, key));
// Balance only decreased once
console.log("Balance:", payments.balance); // 950, not 900

// Naturally idempotent operations
// SET operations are idempotent: SET x = 5 (same result every time)
// DELETE is idempotent: deleting an already-deleted item is fine
// GET is idempotent: reading data has no side effects

// NOT idempotent without a key
// INCREMENT: x++ changes the result each time
// INSERT: creates duplicates
// TRANSFER: moves money multiple times

Interactive Experiment

Try these exercises to build intuition:

  • Add an expiration time to idempotency keys (e.g., keys expire after 24 hours). Why is this necessary for production systems?
  • Classify these HTTP methods as idempotent or not: GET, POST, PUT, DELETE, PATCH. Which ones are naturally idempotent?
  • Modify the payment service to handle concurrent requests with the same key. What happens if two threads check simultaneously?
  • Think about email sending: how would you make a "send welcome email" operation idempotent?

Quick Quiz

Coding Challenge

Idempotent Counter

Write a class called `IdempotentCounter` with a method `increment(operationId)` that increments an internal counter. If the same operationId is used twice, the counter should NOT increment again. Include a `getCount()` method that returns the current count.

Loading editor...

Real-World Usage

Idempotency is critical in every payment system and API:

  • Stripe: Every API request accepts an Idempotency-Key header. If a charge request is retried with the same key, Stripe returns the original result without charging again.
  • AWS SQS: Messages may be delivered more than once. Consumers must handle duplicates using message deduplication IDs.
  • HTTP methods: GET, PUT, and DELETE are designed to be idempotent by the HTTP specification. POST is not, which is why POST-based APIs need explicit idempotency keys.
  • Database migrations: Well-designed migrations use IF NOT EXISTS clauses to be safely re-runnable.
  • Webhooks: Webhook endpoints must be idempotent because providers often retry delivery on failure.

Connections