Revision sheet

Design a digital wallet.

The six things worth holding in your head the night before. Everything else is on the full page.

The opening move — minute one

“A wallet is a stored-value ledger, so every balance is derived from an append-only double-entry log — never a column I mutate. The contract I protect above everything: a balance can’t be spent twice and money is never created or destroyed. At 1B transfers a day I have to shard by account, so the interesting part is making a transfer atomic when the two accounts live on different shards.”

The transfer state machine

INITIATED → PENDING → POSTED → SETTLED
FAILED (from PENDING: insufficient funds / limit / decline — release the hold) · EXPIRED (from PENDING: hold TTL elapsed — sweeper releases) · REVERSED (from POSTED / SETTLED: compensating transfer — never deletion; may drive the account negative)

Must-haves

  1. Designs a double-entry ledger (every movement = balanced debit + credit) and explains why a single mutable ‘balance’ column is wrong.
  2. Prevents double-spend with an atomic conditional debit (row lock / serializable / CAS / single-writer), names the TOCTOU race in check-then-update.
  3. Makes a transfer atomic: same-shard = one ACID transaction; cross-shard = a saga with holds (reserve → credit → confirm) + compensation, and rejects naive 2PC for the right reasons.
  4. Separates available vs total balance with holds, and names the transfer state machine (INITIATED → PENDING → POSTED → SETTLED) and what triggers each transition.
  5. Sizes throughput (~11.6K TPS avg, ~35K peak, 100K+ festival), ledger growth, multi-year retention — and concludes sharding by account_id.

Red flags

  • A single mutable ‘balance’ column instead of a ledger — corrupts under any concurrent write or partial failure.
  • Check-then-debit with no atomicity — two concurrent transfers double-spend the same balance.
  • Crediting the destination before the source is debited, or two separate transactions for a transfer — money created / destroyed on crash.
  • Treating a card / ACH top-up as final, spendable money — a provisional-credit loss waiting to happen.

The ten trap claims — each one is wrong

  1. “a wallet is just a balance column you increment and decrement”
  2. “check the balance, then debit if it is sufficient”
  3. “a transfer is one UPDATE for the sender and one for the receiver”
  4. “just use distributed transactions (2PC) across shards”
  5. “available balance equals total balance”
  6. “a top-up is instantly spendable money”
  7. “eventual consistency is fine for balances”
  8. “reverse a transfer by deleting its ledger rows or subtracting from the balance”
  9. “one big Postgres is fine — sharding is optional”
  10. “publish the event to Kafka, then write the ledger”

The L4 lines — what depth sounds like

Double-Spend / Concurrency Control

Compares pessimistic locking vs optimistic CAS vs single-writer-per-shard under contention, quantifies retry / abort rates at peak, and reasons about isolation-level anomalies (write skew) on the money path

Double-Entry Ledger

Quantifies ledger write volume (~11.6K TPS × 2+ entries), shards by account_id, mitigates hot-account write contention via sub-accounts + batching, and handles cross-currency entries with rate snapshotting

Atomic Cross-Shard Transfer

Addresses the exact half-commit (credit lands, source confirm fails) with a recovery worker, stuck-saga TTL sweeper, compensation ordering, and bounds the worst case to funds held for the hold TTL

Holds & Available Balance

Quantifies hold volume and sweeper cadence, handles partial captures and hold top-offs, and reasons about hold-vs-authorization semantics for pending withdrawals

Provisional Credit & Settlement Risk

Quantifies float exposure and reserve sizing, designs negative-balance recovery (dunning / offset / write-off), and sets availability policy by user risk tier and funding-source type

Reconciliation & Money Conservation

Quantifies reconciliation load across shards and PSPs, replays the append-only ledger for residuals, and defines regulatory reporting for unresolved mismatches and recovered negative balances