A marketplace checkout calls a payment API. Mobile clients on poor networks time out and retry, and some customers are charged twice. Where exactly must the deduplication record be written for this to be fixed?
Show the full answer Hide the answer
What is being tested
Whether you understand that the ordering of the write relative to the side effect is the pattern. Everything else about idempotency is bookkeeping.
The reasoning
The failure being fixed is a process that dies between charging and recording. If you record after the charge, that window exists and the retry charges again. So the record must be durable first.
The correct sequence:
- Client generates an idempotency key for the logical operation — one key per checkout attempt, regenerated only when the user genuinely starts over.
- Server inserts the key into a table with a unique index, in a transaction, along with a
fingerprint of the request body and a status of
in_progress. - If the insert violates the unique constraint, this is a duplicate. Return the stored response if the original completed, or a "request in progress" status if it has not, so the client waits and retries rather than proceeding.
- Only now call the payment processor.
- Persist the response against the key and mark it complete.
The unique index is doing the concurrency control. Two simultaneous requests race on the insert and
exactly one wins — which is why a SELECT then INSERT check is wrong, and why an in-memory cache
is wrong the moment there are two instances, which is always.
Why the other options fail
Recording after confirmation leaves the exact window the bug lives in.
In-memory is not shared across instances and does not survive a restart. It will appear to work in testing and fail in production under precisely the conditions that matter.
Fixing the client is not available. The client cannot distinguish "never arrived" from "succeeded but the response was lost". Retrying is the only correct behaviour open to it, so the server must absorb duplicates. Any design that depends on clients not retrying is depending on something it cannot enforce.
Details that separate a good answer from a complete one
- Store the request fingerprint. If the same key arrives with a different amount, that is a client bug and should error, not silently return the first result.
- Persist the response body, not just completion, so the replay is answered identically.
- Expire keys on a documented window — commonly 24 hours — so a retry a week later has defined behaviour.
- Scope keys per tenant, or one merchant can collide with another.
- Check the internal hops too. The public API is now idempotent; the internal call from the API to the ledger may not be, and the internal client retries.
The framing worth remembering
Exactly-once delivery is not available across a network. At-least-once delivery plus idempotent processing gives an exactly-once effect, and that is the only version of the guarantee that exists.