Interview question
Implement a persisted idempotent FastAPI POST
Implements a database-backed idempotency boundary that replays one response and rejects key reuse with a different request.
TL;DR
Implements a database-backed idempotency boundary that replays one response and rejects key reuse with a different request.
FastAPI headers, request hashing, unique constraints, transaction ownership, response replay, concurrency, and ambiguous outcomes.
Practice the problem like a real interview: restate, reason, implement, and test.
Implement POST /payments with a required Idempotency-Key. The first request creates the payment and stores the response in the same database transaction. A retry with the same key and equivalent body returns the stored status and body. Reusing the key for a different request returns 409. Concurrent duplicates must create one payment.
Next in Python Backend Practical Labs: FastAPI ownership test
key=K, body=A first call -> 201 payment P
key=K, body=A retry -> 201 same payment P
key=K, body=B -> 409 key reused
async def create_payment(command: CreatePayment, key: str, session_factory):
request_hash = command.canonical_hash()
async with session_factory() as session:
async with session.begin():
existing = await find_idempotency(session, command.account_id, key)
if existing:
return existing.replay_or_conflict(request_hash)
payment = Payment.create(command)
session.add(payment)
await session.flush()
response = PaymentOut.from_domain(payment).model_dump(mode="json")
session.add(IdempotencyRecord.completed(
account_id=command.account_id,
key=key,
request_hash=request_hash,
status_code=201,
response_json=response,
))
return 201, response
# Unique(account_id, key) is required.
# Catch the constraint race outside this failed transaction,
# then open a fresh session and replay the committed winner.
The normal path uses an indexed key lookup plus bounded inserts. Storage grows with retained keys, so expiry and archival need a product policy.
Spot a weak answer, missing edge case, or clearer explanation? Send it in.
Say thanks with a standalone, one-time $5 contribution. No account required.