Core Entities
Wallet: (wallet_id, user_id, currency, balance_cents, status=ACTIVE|FROZEN|CLOSED, created_at). One user may have multiple wallets (different currencies). LedgerEntry: (entry_id, wallet_id, amount_cents, entry_type=CREDIT|DEBIT, reference_type=TRANSFER|TOPUP|WITHDRAWAL|FEE, reference_id, balance_after_cents, created_at). The ledger is the authoritative record — balance is derived from ledger entries, not stored directly (double-entry bookkeeping principle). Transfer: (transfer_id, from_wallet_id, to_wallet_id, amount_cents, currency, status=PENDING|COMPLETED|FAILED|REVERSED, idempotency_key, created_at).
Double-Entry Bookkeeping
Every transaction creates two ledger entries: a DEBIT on the sender’s wallet and a CREDIT on the receiver’s wallet. The sum of all CREDIT entries minus all DEBIT entries equals the current balance. This invariant detects data corruption: if it does not hold, something went wrong. Implementation: in a single database transaction: (1) INSERT LedgerEntry (sender, -amount, DEBIT). (2) UPDATE wallet SET balance = balance – amount WHERE wallet_id=sender AND balance >= amount (optimistic check). (3) INSERT LedgerEntry (receiver, +amount, CREDIT). (4) UPDATE wallet SET balance = balance + amount WHERE wallet_id=receiver. (5) INSERT Transfer record. All five operations are atomic. If any fails, the entire transaction rolls back.
Preventing Double Spend
Double spend occurs when the same funds are used twice (network retry causes two transfers). Prevention: idempotency key on every Transfer. Before processing: SELECT transfer WHERE idempotency_key=:key. If found: return existing result. If not: process and INSERT with idempotency_key (unique constraint). Optimistic locking for balance: UPDATE wallet SET balance=balance-amount, version=version+1 WHERE wallet_id=:id AND version=:expected_version AND balance >= amount. If 0 rows affected: concurrent modification — retry. The version check prevents two concurrent transfers from both seeing sufficient balance and both succeeding.
Transaction Limits and Controls
Daily limit: track daily_transferred_cents per wallet per date (Redis: INCR wallet:{id}:daily:{date} by amount; set TTL to midnight). Check before transfer: if daily_transferred + amount > daily_limit: reject. Single transaction limit: if amount > single_tx_limit: reject or require additional authentication (step-up auth). Velocity checks: 3+ failed transfers in 1 hour -> temporarily suspend wallet. Merchant category limits: block wallet from being used at certain merchant categories (gambling, adult content) — user-configurable controls. Store limit rules in a configurable table (not hardcoded) to allow per-user customization and regulatory overrides.
Top-up and Withdrawal
Top-up (load money into wallet): user initiates payment via bank transfer or card. Create a TopupRequest with status=PENDING. On payment confirmation from the payment provider (webhook): atomically credit the wallet and mark TopupRequest=COMPLETED. Never credit before payment confirms. Withdrawal (move money to bank account): debit the wallet immediately (hold funds), create a WithdrawalRequest. Process asynchronously via bank transfer (ACH, SEPA). On bank confirmation: mark COMPLETED. On failure: credit funds back to wallet and mark FAILED. Minimum withdrawal amount and withdrawal fee: apply fee as a separate DEBIT ledger entry (reference_type=FEE).
Interview Tips
- Balance should be computable from the ledger (sum of credits minus debits). Store it as a denormalized cache for performance, but the ledger is the source of truth.
- All balance changes must be inside a database transaction. Never modify two wallets in separate transactions — a crash between them leaves the system in an inconsistent state.
- Idempotency key is mandatory for any money movement. Clients must generate and store the key; servers must handle retries gracefully.
Asked at: Stripe Interview Guide
Asked at: Coinbase Interview Guide
Asked at: Shopify Interview Guide
Asked at: Snap Interview Guide
See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering