Skip to content
Learn/Idempotency

Idempotency

Making operations safe to retry

Key Takeaways

  • An idempotency key uniquely identifies an operation — the same key always produces the same result
  • Store the response alongside the key so duplicate requests return the original result without re-executing
  • Use database-level locking (advisory locks or UNIQUE constraints) to handle concurrent duplicates safely
  • Check for existing keys before acquiring locks to avoid unnecessary contention on the happy path

What is Idempotency?

An operation is idempotent if performing it multiple times has the same effect as performing it once. In the context of APIs, this means that sending the same request twice (or ten times) produces exactly one result — no duplicate orders, no double charges, no repeated side effects.

This is different from simply returning the same response. True idempotency means the *state of the system* doesn't change after the first successful execution, regardless of how many times the request is repeated.

Why It Matters

Networks are unreliable. Clients time out. Load balancers retry. Mobile apps lose connectivity mid-request. When any of these happen, the client doesn't know if the server processed the request or not — so it retries. Without idempotency, that retry creates a duplicate.

In production, this means double charges on payment APIs, duplicate orders in e-commerce systems, or duplicated messages in messaging platforms. Idempotency is not a nice-to-have — it's a correctness requirement for any API that modifies state.

How It Works

The standard approach uses an idempotency key — a unique identifier sent by the client with each request:

  • Client sends a request with an Idempotency-Key header
  • Server checks if this key has been seen before
  • If new: execute the operation, store the key + response, return the result
  • If duplicate: return the stored response without re-executing

The critical challenge is handling concurrent duplicates — two identical requests arriving at the same time. Without proper locking, both requests pass the "is this new?" check simultaneously and both execute, creating duplicates.

Locking Strategies

  • UNIQUE constraint: Insert the idempotency key into a table with a unique constraint. The second insert fails, and you handle the conflict.
  • Advisory locks: Acquire a database-level lock on a hash of the idempotency key before checking. Only one request proceeds at a time.
  • SELECT ... FOR UPDATE: Lock the row within a transaction to prevent concurrent reads.

Common Mistakes

  • Checking without locking: A naive "check then insert" has a race condition. Two concurrent requests both see "key not found" and both execute.
  • Not storing the response: If you only store the key but not the response, you can't return the same result on retries — breaking the idempotency contract.
  • Using request body as the key: The idempotency key should be client-generated and independent of the request body. The same key with a different body should return the *original* response.

Practice This Concept

Apply what you've learned by solving this challenge.

SystemTrials — Backend Engineering Practice That Tests Like Production