DISTRIBUTED SYSTEMS / SYSTEM CONCEPT BRIEF

Idempotency

An operation is idempotent if performing it multiple times has the same effect as performing it once.

IntermediatePhase 05 / Topic 12 of 17RequirementsTrade-offsFailure modes
01

Overview

An operation is idempotent if performing it multiple times has the same effect as performing it once. In distributed systems, retries are unavoidable: a client times out and does not know if the server processed the request, a message broker redelivers a message, or a user double-clicks. Idempotency makes these duplicates harmless.

Some operations are naturally idempotent (set status = shipped, PUT a resource, DELETE by ID). Others, like charging a card or incrementing a balance, need an idempotency key: a unique ID for the logical operation stored with its result, so a repeated request returns the stored result instead of acting again.

An elevator button

Pressing the button for floor 5 once or ten times has the same result: the elevator goes to floor 5 once. A vending machine that drops a snack every press is not idempotent.

02

When to use it

  • Payments and any money movement.
  • APIs that clients retry after timeouts.
  • Message consumers with at-least-once delivery.
  • Webhooks, which are often delivered more than once.
03

Where it shows up in interviews

Safe retries

Recognize it when: client timed out; did the payment happen?

  • Design a payment API
  • Design an order placement service
At-least-once consumers

Recognize it when: messages may be delivered twice.

  • Design a notification system
  • Design an event-driven order pipeline
04

Where it is used in real software

Stripe Idempotency-Key

Clients send an Idempotency-Key header; Stripe stores the first response for 24 hours and replays it for retries.

AWS APIs

Many AWS APIs accept a ClientToken so retried create calls do not create duplicate resources.

Kafka idempotent producer

Sequence numbers per producer and partition let brokers drop duplicate sends.

05

Key terms

Idempotent
f(f(x)) = f(x): repeating has no additional effect.
Idempotency key
Client-generated unique ID for one logical operation.
Deduplication table
Stores processed keys and their results.
At-least-once
Delivery that may duplicate; requires idempotent processing.
06

How it works, step by step

  1. 1
    Client generates a key

    A UUID per logical operation, reused on retries.

  2. 2
    Server checks the key

    If seen and complete, return the stored response.

  3. 3
    Reserve the key atomically

    Insert with a unique constraint; concurrent duplicates fail or wait.

  4. 4
    Perform the operation and store the result

    In the same transaction as the business write when possible.

  5. 5
    Expire old keys

    Keep them for longer than the maximum retry window.

07

A retried payment with and without idempotency

Client sends POST /charges, the response is lost, client retries

Step 1 / 3
AttemptWithout keyWith Idempotency-Key: k1
1Card charged $50, response lostCard charged $50, result stored under k1, response lost
2 (retry)Card charged $50 againKey k1 found: return stored result, no charge
Total charged$100 (bug)$50 (correct)

NOWAttempt: 1 | Without key: Card charged $50, response lost | With Idempotency-Key: k1: Card charged $50, result stored under k1, response lost

Retries are only safe when the server can recognize the repeated operation.

08

Implementation

// Express-style handler with an idempotency table:// CREATE TABLE idempotency_keys (key TEXT PRIMARY KEY, status TEXT, response JSONB, created_at TIMESTAMPTZ DEFAULT now());export async function createCharge(req: Request, res: Response) {  const key = req.header("Idempotency-Key");  if (!key) return res.status(400).json({ error: "Idempotency-Key header required" });   const inserted = await db.query(    "INSERT INTO idempotency_keys (key, status) VALUES ($1, 'processing') ON CONFLICT (key) DO NOTHING RETURNING key",    [key],  );  if (inserted.rowCount === 0) {    const existing = await db.query("SELECT status, response FROM idempotency_keys WHERE key = $1", [key]);    if (existing.rows[0].status === "processing") return res.status(409).json({ error: "Request in progress" });    return res.status(200).json(existing.rows[0].response); // replay original result  }   const charge = await payments.charge(req.body.customerId, req.body.amountCents, { idempotencyKey: key });  await db.query("UPDATE idempotency_keys SET status = 'done', response = $2 WHERE key = $1", [key, charge]);  res.status(201).json(charge);}
09

Complexity and performance

Key lookupO(1) indexed

One extra read/write.

Key retention> max retry window

Often 24 hours to 7 days.

10

Trade-offs

Storage and latency vs safety

Storing keys adds a write per request but prevents costly duplicates.

Key scope

Keys must be scoped per client or account to avoid collisions and abuse.

11

Variants and related techniques

Natural idempotency

Design operations as set-to-value rather than increment.

Conditional writes

Only apply if version matches (optimistic concurrency).

Consumer dedupe by message ID

Record processed message IDs in the same transaction as the side effect.

12

Common mistakes

  • Generating a new key on each retry.

    Fix: The key must be created once per logical operation and reused.

  • Checking and writing non-atomically.

    Fix: Use a unique constraint or atomic insert to reserve the key.

  • Same key with a different payload.

    Fix: Store a request hash and reject mismatches.

13

Interview questions

How do you make a payment API safe to retry?

Require a client-generated idempotency key, reserve it atomically before processing, store the result with the key, and return the stored result for any repeat. Pass the same key to downstream payment providers.

How do message consumers handle duplicate delivery?

Store processed message IDs (or business keys) in the same transaction as the side effect, and skip messages already recorded; or make the side effect naturally idempotent, like an upsert.

14

Practice problems

ProblemDifficultyWhat it trains
Classify 10 operations as idempotent or notEasySemantics.
Implement idempotent order creationMediumAtomic key reservation.