intermediate 2 min answer

Design an order submission API that is safe when the client cannot tell whether its request succeeded. What exactly do you store, and when?

idempotencyapi-designretries
Show the full answer Hide the answer

What the interviewer is testing

Whether you know that "make it idempotent" is a design with specific failure modes, not a checkbox.

The core design

The client generates an idempotency key — a UUID — once per logical order, and sends it as a header on every attempt including retries. The server:

  1. Attempts to insert the key into an idempotency table with status in_progress, using a unique constraint on the key.
  2. If the insert succeeds, this is the first attempt. Process the order.
  3. If it conflicts, this is a retry. If the stored status is complete, return the stored response verbatim. If it is in_progress, return 409 so the client retries later rather than racing.
  4. On completion, write the response body and status into the idempotency row in the same transaction that creates the order.

The detail that decides whether it works

That last point is the whole answer. If the order is committed in one transaction and the idempotency record in another, a crash between them leaves an order with no record of its key — so the retry creates a second order. Same-transaction is what makes the guarantee real.

If the order creation genuinely cannot share a transaction with the key store — different databases — then you need an outbox: write both to the same database, and have a separate process propagate. Two independent writes cannot be made atomic by care.

Details that get missed

Payload mismatch. The same key with a different request body means the client has reused a key for a different order. Return 422, do not process. Store a hash of the request to detect this.

Retention. The key must outlive any plausible retry, which includes a client that retries after a deployment or a queue that redelivers hours later. 24 hours is a common floor; the store needs a TTL or it grows without bound.

Response fidelity. Store the status code and the body. A retry that returns 200 with an empty body has told the client nothing, and it will retry again.

Concurrent duplicates. Two attempts arriving simultaneously is the common case under a retry storm, not an edge case. The unique constraint is what serialises them; an application-level "check then insert" has a race between the check and the insert.

Follow-up you should expect

"What if the client does not send a key?" Then the endpoint is not idempotent and you must say so. Some APIs derive a key from a natural business identifier — customer, cart, minute-bucket — which works but is coarser and will occasionally reject a legitimate second order.