Ch. 19 · System Design

System Design: Idempotent APIs

Make retried requests safe with idempotency keys, store the result per key, and return the original response on a repeat.

~2 min readadvancedupdated Oct 5, 2026

Networks drop responses, clients time out and retry, and a retried write can run twice. An idempotent API makes the repeat produce the same effect as the first call, so a client can safely retry without creating duplicate orders, payments or messages.

Before you start

You should understand request lifecycles and basic storage. This article is conceptual with small illustrations rather than a full implementation.

Step-by-step walkthrough

Step 1: Accept a client-generated key

The client sends an idempotency key — a unique value per logical operation, not per attempt — usually in a header. On a repeat with the same key, the server recognizes the operation and does not perform the side effect again. The key must be generated once and reused across retries.

Step 2: Store the result, not just a flag

Persist the key together with the outcome, so a retry returns the original response rather than re-executing. If you store only “seen”, the retry has no body to return and can return an error or a different result. Store the key, the status and the response, and make the store atomic with the side effect.

Step 3: Scope and expire the key

Scope keys to the caller and operation so two clients cannot collide, and expire them after a window long enough to cover retries. A key that is too broad lets one client’s retry match another’s, and a key kept forever grows the store without benefit.

Worked scenario

The server records the key and replays the stored response.

POST /payments
Idempotency-Key: 8f2c1a

-> first request: charge created, store {key: 8f2c1a, status: 201, body}
-> retry, same key: return stored {status: 201, body}, do not charge again
Text

Walk through the example

The first request performs the charge and stores the key with its response. A retry with the same key finds the record and returns the stored response, so the customer is charged once. If the two requests race, a unique constraint on the key lets one win and the other read the stored result.

Common mistake

Generating the key per attempt, which defeats the purpose because each retry looks like a new operation. Another is storing only a marker and then returning a fresh response, which may differ from the original. Also, a key with no unique constraint lets concurrent retries both proceed.

Verify the behavior

Send the same request twice with one key and assert a single side effect and identical responses. Send concurrent requests with the same key and confirm a unique constraint serializes them. Assert a different key creates a separate effect, and that an expired key allows a new operation.

Interview exercise

Where should the idempotency key be generated, the client or the server?

Answer and reasoning

The client, because it knows the logical operation and whether a retry is a repeat of the same intent. A server-generated key is different on each request, so it cannot identify a repeat. The client generates one key per operation and reuses it across every retry and transport attempt.

Continue learning

Compare the messaging version in Idempotency keys and API resource contracts. Read the Stripe idempotency guidance and try the System design interview questions.

More in System Design

read ✓System Design · hard

System Design: Bloom Filters

Use a Bloom filter to skip lookups with a tiny memory footprint, and understand its false-positive-only guarantee.

~2 min readread →
esc