Step 1 Understand it
Why interviewers ask this. It tests correctness under failure — , , , and audit trails — where a small bug means real money.
Watch it happen
Read the animation as text
- Checkout asks to charge £30, sending an idempotency key, k-91. The payment service creates a PENDING record; a UNIQUE constraint on the key means there can only ever be one.
- It calls the payment provider, which handles the card details — you never store raw card numbers.
- The call times out. Was the card charged or not? Unknown. Checkout retries — with the SAME key.
- The unique key finds the existing payment, so the service does NOT charge again — it returns the current status. No double charge.
- The provider's webhook arrives: succeeded. In ONE transaction the status becomes SUCCEEDED and balanced double-entry ledger rows are written. An outbox event tells the order service.
- Every night a reconciliation job compares your ledger with the provider's settlement file, and flags anything that doesn't match.
Requirements
Functional
- Accept a payment for an order through a (PSP) that talks to the card networks.
- Record every money movement in a .
- Refunds; with the provider's reports.
Non-functional
- Correctness over speed: no double charges, no lost money.
- and a full audit trail.
- Secure ( rules for card data) and .
Estimates
- 1 million payments a day ≈ 12 per second on average: the volume is modest; correctness is the hard part.
API
POST /payments {order_id, amount, currency, idempotency_key} → payment statusWebhook from the provider: payment succeeded or failed
Data model
- payments (id, order_id, amount, currency, status, idempotency_key UNIQUE)
- ledger_entries (id, account_id, payment_id, debit, credit, created_at), append-only and double-entry
Start simple, then scale
- Version 1: call the payment provider and mark the order 'paid'.
- Version 2: a payments table with statuses and .
- Version 3: a double-entry ledger, written in the same transaction as the status change.
- Version 4: , an , reconciliation, and handling results that are unknown.
Picture it
sequenceDiagram participant C as Client participant P as Payment service participant DB as Database participant PSP as Payment provider C->>P: pay £50 (idempotency key k1) P->>DB: insert payment PENDING (key k1 must be unique) P->>PSP: charge £50 (key k1) PSP-->>P: succeeded P->>DB: one transaction - status SUCCEEDED, ledger debit and credit P-->>C: paid Note over P,PSP: every night, reconcile the ledger with the provider's reports
The design, step by step
- The payment service receives the with an idempotency key; a unique constraint in the blocks duplicates.
- It creates a PENDING payment record, then calls the payment provider. The provider handles the card details, so you never store raw card numbers.
- The provider's result ( or webhook) updates the status, and balanced double-entry ledger rows are written in the same transaction.
- The order service is told through an outbox .
- A nightly reconciliation job compares the ledger with the provider's settlement files.
Deep dives
- Idempotency keys plus unique constraints make every retry safe.
- Double-entry ledger: every movement is a debit and an equal credit; balances are sums; entries are never edited, only added (corrections are new entries).
- Provider timeout: the result is unknown, so retry with the same idempotency key or ask the provider for the status before doing anything else.
- Store money as whole minor units (pence) together with the currency.
Common mistakes
- Retrying a timed-out charge with a brand-new request.
- Floats for money.
- Updating balances in place, with no ledger history.
Step 2 Go further (free, optional)
Optional. The notes above are written to be enough on their own. These free resources give a second explanation and extra practice.
-
Watch
ByteByteGo: payment system design
ByteByteGo · YouTube
-
Read
Hello Interview: payment system
hellointerview.com · free guides
- Read
Step 3 Practise
Try each exercise yourself first: on paper, out loud or in code. Then open the answer and compare it with yours.
-
ExplainSay it in plain words in under a minute, with no notes.
Explain how idempotency keys prevent a double charge, using a timeout example.
Show answer
The checkout sends POST /payments for order 77, £50, with an idempotency key the generated once for this purchase: "pay-order-77".
1. The payment service inserts a PENDING payment row with that key. The key column is UNIQUE, so this can only succeed once.
2. It calls the payment provider, passing the same key along.
3. The provider charges the card, but the response is lost: a timeout. The service doesn't know whether the card was charged.
4. The client (or the service itself) retries, with the same key.
• At our end: the insert hits the unique constraint, so we find the existing payment instead of creating a second one, and return its status, or carry on from where it got to.
• At the provider: they see a key they have already processed, and return the original result instead of charging again.
5. If the result is still unknown, ask the provider "what happened to pay-order-77?" before anything else.Without the key, the retry looks like a new purchase, and the customer is charged £100. With it, any number of retries charge .
-
DrawSketch the diagram (or class diagram) from memory.
Draw client → payment service → provider, with the ledger database, outbox and reconciliation job.
Show answer
Writes to the payment, the ledger and the outbox happen in one database transaction, so the order service is told exactly when, and only when, the money has really moved. Reconciliation is the safety net that compares our ledger with the provider's own records every day.
textClient ── POST /payments + Idempotency-Key ──► Payment service │ 1. insert PENDING (key UNIQUE) ▼ ┌─── Payments DB ───────────────┐ │ payments (idempotency_key UQ) │ │ ledger_entries (append-only) │ │ outbox │ └───────────────────────────────┘ │ 2. charge, same key ▼ Payment provider (PSP) ──► card networks │ 3. response or webhook ▼ one transaction: status + ledger rows + outbox event │ outbox relay ──► "PaymentSucceeded" ──► Order service Nightly: Reconciliation job ── compares ──► our ledger vs ──► provider's settlement file mismatches ──► alerts, manual review -
ApplyUse it in a real design question and say exactly where it fits.
Write the ledger rows (debits and credits) for a £50 purchase followed by a £20 partial refund.
Show answer
Double entry: every movement is written twice, a debit on one account and an equal credit on another, so each transaction balances (total debits = total credits) and the books as a whole always sum to zero. Rows are only ever added: the refund doesn't edit the purchase; it is a new transaction pointing at it.
Here the money the provider will pay us sits in a "provider receivable" account, and revenue is the shop's sales account. After both transactions, provider receivable has a £30 debit balance (50 − 20) and sales revenue a £30 credit balance: the customer has paid £30 net. Amounts are in pence, with the currency on every row.
textentry txn account debit credit currency note ───── ───────── ──────────────────── ────── ────── ──────── ───────────────────── 1 pay_P1 provider_receivable 5000 GBP £50 purchase, order 77 2 pay_P1 sales_revenue 5000 GBP 3 refund_R1 sales_revenue 2000 GBP partial refund of P1 4 refund_R1 provider_receivable 2000 GBP pay_P1: debits 5000 = credits 5000 ✓ refund_R1: debits 2000 = credits 2000 ✓ Balances: provider_receivable 5000 − 2000 = 3000 (debit) sales_revenue 5000 − 2000 = 3000 (credit) -
Trade-offsSay what it costs, when not to use it, and the alternative.
Why choose strong consistency and a here, even if it costs some availability?
Show answer
Because in payments, a wrong answer is far worse than a slow one.
• Correctness is the product: a double charge or a vanished payment costs real money, customer trust, and sometimes regulatory trouble. An store that accepts two conflicting writes during a network split and "merges later" would mean deciding after the fact which customer charge to reverse.
• The guarantees needed are exactly what relational databases give: transactions (the payment status, the ledger rows and the outbox event together), unique constraints (idempotency keys), foreign keys, and checks (amounts positive, every transaction balanced).
• Auditing and reconciliation need precise, queryable history: and sums over the ledger.
• The volume is modest (about 12 payments a second on average), which one relational primary with handles easily, so you don't need a distributed store's scale.What is given up: during a failure, payments may be refused or delayed for a moment. That is recoverable: the client retries with the same idempotency key, or the order shows "payment processing". Lost or doubled money isn't recoverable so easily, so the trade is clear.
Step 4 Interview-ready, then prove it
Your interview answer
About 20 to 40 seconds, said out loud. Practise it until it sounds like you, not like notes.
Correctness first. Every payment request carries an idempotency key enforced by a unique constraint. The payment service records a pending payment, calls the payment provider — which holds the card data, so we never store card numbers — and on the result writes balanced double-entry ledger rows in one ACID transaction. Events go out through an outbox, and daily reconciliation against the provider catches any mismatch.
Follow-up questions
The call to the provider timed out. Did the customer pay?
We don't know. Never retry with a new request: retry with the same idempotency key, or ask the provider for the payment's status first.
Why double-entry?
Every debit has a matching credit, so the books always balance, mistakes show up as imbalances, and the history is a complete audit trail.
Tick each check when you can do it without notes
A topic is finished when you pass all four checks, not when you finish a video. Be strict: if you needed your notes, it is not ticked yet.
- Explain Say it in plain words in under a minute, with no notes.
- Draw Sketch the diagram (or class diagram) from memory.
- Apply Use it in a real design question and say exactly where it fits.
- Trade-offs Say what it costs, when not to use it, and the alternative.
0 of 4 checks ticked.
Saved in this browser. Back it up or move it to another device.