The client retried POST /payment. Did we charge twice?
“A mobile client calls `POST /payments`, the gateway times out after 30 s, and the client retries. How do you guarantee the customer is charged once?”
What this tests
- Whether the candidate sees that a timeout is ambiguous: the first request may or may not have succeeded
- Idempotency keys as a concrete mechanism: who generates them, where they are stored, for how long
- Understanding that the provider call must be covered too, not only the local database write
- Knowing the difference between natural idempotency (PUT) and engineered idempotency (POST + key)
Answers by level
Read the beginner answer first and notice what is missing.
A timeout tells the client nothing about whether the server finished; the charge may have gone through after the gateway gave up. So the client generates an idempotency key (a UUID per payment attempt, not per click) and sends it in a header. The server stores the key with the request fingerprint and, once done, the response. A retry with the same key returns the stored response instead of charging again.
The "check then charge" version has a race: two concurrent retries both see no payment and both charge. The key must be reserved atomically — insert the key row first (unique constraint), then do the work. A second insert fails and waits for or reads the first result.
The payment provider call needs the same treatment: pass the key to the provider (Stripe-style Idempotency-Key) so a retry between our commit and their response does not double-charge on their side either.
Green flags · Red flags
- Says immediately that the timeout is ambiguous, not "the request failed"
- Client-generated key per attempt, stored server-side with the response
- Atomic reservation of the key (unique constraint) rather than check-then-act
- Covers the provider call with its own idempotency key
- Mentions TTL and per-user scoping
- "We just check if the payment exists before charging." (a race, not a guarantee)
- "Kafka gives you exactly-once, so we don't need idempotency."
- Proposes disabling client retries as the whole solution
- Keys generated on the server per request, which makes a retry a new key