# System Design Interview: Design a Digital Wallet and Payment System

Source: https://www.techinterview.org/post/3233463059/system-design-digital-wallet-payment-system/
Updated: 2026-04-17 · techinterview.org

## What Is a Digital Wallet?

A digital wallet stores funds and enables transfers between users and merchants. Examples: PayPal, Venmo, Apple Pay, Google Pay, Cash App. Core challenges: exactly-once transactions (no double charges or double credits), consistent balance across concurrent operations, and preventing fraud while maintaining low latency.

- [Airbnb Interview Guide](https://techinterview.org/airbnb-software-engineer-interview/)

- [Lyft Interview Guide](https://techinterview.org/lyft-software-engineer-interview/)

- [Uber Interview Guide](https://techinterview.org/uber-software-engineer-interview/)

- [Shopify Interview Guide](https://techinterview.org/shopify-software-engineer-interview/)

- [Coinbase Interview Guide](https://techinterview.org/coinbase-software-engineer-interview/)

- [Stripe Interview Guide](https://techinterview.org/stripe-software-engineer-interview/)

## System Requirements

### Functional

  - Add funds via bank transfer or credit card

  - Send money to another user

  - Pay merchants

  - View transaction history

  - Link and manage bank accounts

### Non-Functional

  - 100M users, 10K transactions/second

  - No double charges, no lost transfers

  - Transaction finality within 5 seconds

  - Compliance: PCI-DSS for card data, AML for large transfers

## Core Data Model — Double-Entry Accounting

Never update a balance directly. Use a ledger (journal) of immutable transactions:


```
accounts: id, user_id, currency, balance (cached sum)
transactions: id, idempotency_key, status, created_at
ledger_entries: id, transaction_id, account_id, amount, entry_type(debit/credit)
```

Every transaction creates exactly two ledger entries: one debit (balance decreases) and one credit (balance increases). The sum of all entries for an account equals its balance. This double-entry model means the ledger always balances — total debits = total credits across all accounts. Auditors can verify correctness by recomputing balances from the ledger.

## Transfer Implementation


```
def transfer(sender_id, receiver_id, amount, idempotency_key):
    # Idempotency: check if this transfer already happened
    if Transaction.exists(idempotency_key):
        return Transaction.get(idempotency_key)

    with db.transaction():
        # Lock both accounts (order by ID to prevent deadlock)
        sender = Account.lock(min(sender_id, receiver_id))
        receiver = Account.lock(max(sender_id, receiver_id))

        if sender.balance < amount:
            raise InsufficientFunds()

        txn = Transaction.create(idempotency_key=idempotency_key)
        LedgerEntry.create(txn, sender, -amount, 'debit')
        LedgerEntry.create(txn, receiver, +amount, 'credit')

        sender.balance -= amount    # update cached balance
        receiver.balance += amount

        txn.status = 'completed'
    return txn
```

Lock ordering (min account ID first) prevents deadlocks when two concurrent transfers involve the same two accounts in opposite directions.

## Idempotency

The caller provides an idempotency_key (UUID) with each transfer. If the network times out and the caller retries, the second call sees the existing transaction and returns it — no double transfer. The idempotency check must be inside the same transaction as the transfer to prevent TOCTOU (time-of-check-time-of-use) race conditions.

## External Transfers (Bank Deposits/Withdrawals)

Bank transfers via ACH take 1-3 business days and can be reversed (insufficient funds, fraudulent). Wallet credits are "pending" until ACH settles. Two-phase approach: credit the wallet with "pending" status immediately (good UX), set a hold on spending (cannot withdraw), lift the hold when ACH confirms settlement. On ACH reversal: debit the wallet and notify the user.

## Fraud Detection

Rules evaluated in real-time before each transaction:

  - Velocity: more than 10 transfers in 1 hour → flag for review

  - Amount: single transfer over $10,000 → AML report (CTR)

  - Geo anomaly: transfer from country never seen for this user

  - ML model: predict fraud probability from 50+ features

Flagged transactions are held for manual review. High-confidence fraud: auto-decline and freeze account.

## Scaling Balances

The cached balance column (accounts.balance) is a hotspot for high-volume wallets (a merchant receiving thousands of payments per second). Solutions: (1) Optimistic locking: use a version column; retry on conflict — works for moderate contention. (2) Balance sharding: split the balance across N virtual sub-accounts; aggregate for read, distribute writes. (3) Asynchronous balance update: update ledger entries synchronously, update cached balance asynchronously from Kafka — accept slight staleness for display balance.

## Interview Tips

  - Double-entry ledger is the correct financial data model — mention it explicitly.

  - Lock ordering by account ID prevents deadlocks.

  - Idempotency key is the primary exactly-once mechanism.

  - Pending state for external transfers handles ACH reversal risk.
