System Design Problem

Design a Cryptocurrency Exchange

Commonly Asked By:CoinbaseBinanceRobinhoodKraken

Interview Setup

Interview Prompt

Design a cryptocurrency exchange handling 100K orders/sec peak across 500 trading pairs, with in-memory matching, hot/cold wallet architecture, and 2M concurrent WebSocket price feeds.

Clarifying Questions (ask before designing)

QuestionWhy it matters
Custodial (exchange holds keys) or non-custodial (user holds keys)?
  • Custodial requires hot/cold wallet security
  • non-custodial is DEX model.
Matching engine: price-time priority like a stock exchange?100K orders/sec needs in-memory order book per pair with WAL.
How many blockchain confirmations before crediting deposit?
  • BTC: 3 confirmations (~30 min)
  • ETH: 12 blocks (~3 min). Affects user experience.
Withdrawal limits and multi-sig for large amounts?
  • Hot wallet holds 5% of assets
  • large withdrawals need cold wallet multi-sig.

Scope

In scope

  • Variant of stock exchange + wallet
  • Hot/cold wallet
  • Blockchain confirmation
  • Order matching
  • Capacity estimation with shown math

Out of scope (state explicitly)

  • Fraud ML model training: rules engine is sufficient unless specifically asked
  • Merchant onboarding / KYC workflows
  • Building a PSP or bank from scratch

Functional Requirements

Start by asking your interviewer whether you are designing the matching engine, custody layer, or both. Clarify regulatory scope (KYC/AML, fiat on-ramps) and confirm idempotency plus audit trail requirements before you sketch boxes because money correctness is non-negotiable.

  • Order placement: limit, market, stop-loss orders for crypto pairs
  • Order matching engine: price-time priority
  • Wallet management: deposit, withdraw crypto (on-chain) and fiat
  • Hot wallet (online, for fast withdrawals) and cold wallet (offline, for security)
  • Real-time order book and trade feed (WebSocket)
  • Portfolio: view balances, P&L, transaction history
  • KYC/AML: identity verification, transaction monitoring
  • Multi-factor authentication (2FA, hardware keys)
  • Trading fees with tiered pricing (maker/taker)
  • Staking and lending features

Non-Functional Requirements

Your interviewer will test whether you understand the latency split: sub-millisecond matching vs async settlement. Lead with "balances must never be wrong" and hot/cold wallet blast-radius before discussing throughput.

  • Low Latency: Order matching < 1ms; API response < 50ms
  • High Availability: 99.99% (downtime during volatility = massive losses)
  • Security: Cold wallet for 95% of assets; HSM for key management
  • Consistency: Account balances must NEVER be wrong
  • Auditability: Every transaction traceable for regulatory compliance
  • Throughput: 100K orders/sec peak (during market events)

Capacity Estimations

Size the matching engine per trading pair because single-threaded in-memory books avoid lock contention at 100K orders/sec peak. Wallet and WebSocket fan-out numbers justify why settlement stays async.

MetricCalculationValue
Registered usersGiven50M
DAUGiven5M
Trading pairsGiven500
Orders / sec (peak)Derived from daily volume ÷ 86400 (+ peak factor)100K
Trades / sec (peak)Derived from daily volume ÷ 86400 (+ peak factor)50K
WebSocket connectionsGiven2M concurrent
Wallet transactions / day1M ÷ 864001M
Hot wallet balanceGiven5% of total assets

Architecture Diagram

In the room: draw matching engine and wallet layer as separate boxes: the hot wallet holds approximately 5% of assets, while cold storage limits breach blast radius.

In the interview, draw the matching engine and wallet layer as separate boxes with Kafka between them. A crypto exchange is two systems welded together: a sub-millisecond matching engine where price-time priority and WAL durability are non-negotiable, and a custodial wallet layer where hot and cold tiers limit blast radius on breach. Kafka bridges the match event to async settlement so balance updates never block the order ACK path.

At 100K orders/sec peak across 500 pairs, each pair runs single-threaded in memory without lock contention, yielding predictable microsecond latency. WebSocket feeds fan out trade and order-book updates to 2M concurrent connections; edge throttling prevents a volatility spike from melting the broadcast tier.

Loading...

Component Deep Dives

Money paths split into a synchronous commit phase (reserve funds, match, WAL append) and an async settlement phase (balance deduction, fees, notifications). The sections below trace that lifecycle through the matching engine, wallet tiers, race-condition guards, and the Kafka topics that make recovery after a crash replayable.

Order Lifecycle

The Order Service validates and reserves funds in Redis before the matching engine ever sees the order, meaning a failed reserve returns immediately without polluting the book. Settlement consumers apply PostgreSQL ledger updates idempotently from trade events.

1. User submits order: POST /api/orders {pair:BTC_USDT, side:buy, price:50000, qty:0.5}
2. Order Service:
   a. Validate: user authenticated, pair exists, qty > min
   b. Reserve funds: Lua script in Redis → USDT_available -= 25000
   c. If Redis reserve fails → reject immediately
   d. Persist order (status=open) to PostgreSQL
   e. Route to matching engine for this pair

3. Matching Engine (single-threaded, in-memory per pair):
   a. Insert order into order book (TreeMap of price levels)
   b. Match against opposite side: buy $50K vs best ask $49,950 → MATCH at $49,950
   c. Generate trade event. Write to WAL before returning.
   d. Publish trade event to Kafka

4. Post-Trade Settlement (async from Kafka):
   a. Update balances, deduct fees (maker 0.1%, taker 0.15%)
   b. All inside PostgreSQL transaction

5. WebSocket broadcast: order book update, trade feed, user events

Matching Engine Deep Dive

Price-time priority means the best bid meets the best ask at the maker's price, and FIFO queueing within a price level prevents queue-jumping. Single-threaded execution per pair eliminates lock contention and makes cancel-during-match semantics deterministic.

Data structure: Two TreeMaps (Red-Black Trees) per pair
  Bids: sorted by price DESC (highest first), then time ASC
  Asks: sorted by price ASC (lowest first), then time ASC

Price-Time Priority: same price → earlier order fills first (FIFO)

Single-threaded: ONE thread per trading pair
  No locks needed → predictable µs latency
  100 pairs yields 100 independent threads

WAL: Every match written to append-only file BEFORE publish
  On crash → replay WAL → rebuild order book state

Performance: 100K+ matches/sec per pair (in-memory, single-threaded)

Deposit & Withdrawal Flow

Deposits wait for chain confirmations before crediting, so a reorg deeper than the threshold reverses pending credits. Withdrawals drain the hot wallet first, while large amounts queue for cold-wallet multi-sig with human review on anomaly signals.

DEPOSIT:
  1. Generate HD wallet address (unique per user per chain)
  2. Blockchain Watcher detects incoming transaction
  3. Wait for N confirmations (BTC:3, ETH:12, SOL:32)
  4. Credit user: UPDATE balances SET available = available + amount
  5. Edge case: chain reorg → reverse credit if tx disappears

WITHDRAWAL:
  1. User submits withdrawal (2FA required)
  2. Deduct from available balance (PG transaction)
  3. Hot wallet service signs transaction (HSM multi-sig 2-of-3)
  4. Broadcast to blockchain
  5. Monitor for confirmation
  6. If hot wallet balance < withdrawal amount: queue and trigger cold-to-hot replenishment

Race Conditions

Double-spend, cancel-during-match, and withdraw-while-trading are the three classic failure modes. Redis Lua and PostgreSQL SELECT FOR UPDATE split available vs reserved balances so concurrent orders cannot oversell the same coins.

1. Double-Spend: User has 1 BTC, submits two sell orders simultaneously
   Solution: Redis Lua atomic check-and-decrement (single-threaded, no race)
   PostgreSQL: authoritative backup with SELECT FOR UPDATE

2. Order Cancel During Matching:
   Solution: Single-threaded engine → cancel queued behind match
   If order partially filled before cancel: fill matched, cancel remainder

3. Withdrawal After Balance Used in Trade:
   Solution: Withdrawal deducts from available, trade from reserved (different balance pool)
   PostgreSQL transaction ensures atomicity

Blockchain Reorg Handling

The system waits for N confirmations before crediting deposits (such as 3 confirmations or approximately 30 minutes on Bitcoin, and 12 confirmations or approximately 3 minutes on Ethereum). The Blockchain Watcher compares block hashes continuously. If a reorg is detected deeper than N blocks, the system reverses pending credits. The exchange never permits users to trade against unconfirmed deposits.

Event Bus Design (Kafka)

Trade events partition by trading pair to preserve per-book ordering, allowing settlement, WebSocket broadcast, and audit consumers to replay from the same durable log. WAL fsync precedes Kafka publish so a crash never loses a matched trade that was ACKed to the client.

Topic: trade-events
  Partitions: 128 (partition by trading_pair to preserve per-pair ordering)
  Retention: compacted by trade_id (compliance; replay rebuilds order book state)
  Producers: Matching Engine immediately after WAL append (before ACK to client)
  Payload: {trade_id, pair, price, qty, buyer_id, seller_id, maker_order_id, taker_order_id}
  Consumers: Settlement Service, WebSocket broadcaster, audit log, market data feed

Topic: order-events
  Partitions: 64 (partition by order_id)
  Events: placed, partially_filled, cancelled, expired
  Consumers: Order Service state sync, user notification WebSocket

Topic: wallet-events
  Events: deposit_confirmed, withdrawal_submitted, withdrawal_broadcast, balance_adjusted
  Consumers: Blockchain Watcher, AML monitoring, Proof-of-Reserves aggregator

Match path: in-memory match → WAL fsync → publish trade-events → return fill to client
  Settlement (balance update, fees) consumes trade-events asynchronously

API Design

REST APIs handle order placement and account balances, while WebSocket streams power real-time order-book and trade execution feeds. Every order submission must provide an idempotency key on POST /orders because duplicate submissions during market volatility spikes are a classic failure mode.

HTTP
POST /api/orders              → Place order {pair, side, type, quantity, price}
DELETE /api/orders/{id}       → Cancel order
GET /api/orders?status=open   → Open orders
GET /api/orderbook/{pair}     → Order book snapshot
GET /api/trades/{pair}        → Recent trades
GET /api/wallet/balances      → Portfolio balances
POST /api/wallet/withdraw     → Initiate withdrawal (2FA required)

# WebSocket streams
WS /ws/orderbook/{pair}       → Real-time order book
WS /ws/trades/{pair}          → Real-time trades
WS /ws/user                   → User order updates, balance changes

Common Error Responses

400 Bad Request: invalid input, missing required fields, or malformed JSON payload
401 Unauthorized: missing or invalid authentication token or API key
403 Forbidden: authenticated caller lacks required permissions for this resource
404 Not Found: requested resource ID does not exist
409 Conflict: duplicate write or version conflict, retry with a unique idempotency key
422 Unprocessable Entity: syntactically valid request failed semantic business validation
429 Too Many Requests: rate limit quota exceeded, client should honor Retry-After header
500 Internal Error: unexpected server failure, retry safely with an idempotency key
503 Service Unavailable: downstream dependency is unavailable or overloaded, retry with exponential backoff

Data Model

PostgreSQL (Ledger: Source of Truth)

ACID transactions protect all financial ledger operations. DECIMAL(28,18) handles cryptocurrency precision (supporting 18 decimal places for Ethereum), while SELECT FOR UPDATE locks user records to prevent double-spending.

SQL
CREATE TABLE balances (
    user_id   UUID, currency  TEXT,
    available DECIMAL(28,18) NOT NULL DEFAULT 0 CHECK (available >= 0),
    reserved  DECIMAL(28,18) NOT NULL DEFAULT 0 CHECK (reserved >= 0),
    PRIMARY KEY (user_id, currency)
);

CREATE TABLE orders (
    order_id    UUID PRIMARY KEY, user_id UUID NOT NULL,
    pair        TEXT NOT NULL, side TEXT NOT NULL,
    order_type  TEXT NOT NULL, price DECIMAL(28,18),
    quantity    DECIMAL(28,18) NOT NULL,
    filled_qty  DECIMAL(28,18) DEFAULT 0,
    status      TEXT DEFAULT 'open',
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE trades (
    trade_id    UUID PRIMARY KEY, pair TEXT NOT NULL,
    buyer_id    UUID NOT NULL, seller_id UUID NOT NULL,
    price       DECIMAL(28,18) NOT NULL,
    quantity    DECIMAL(28,18) NOT NULL,
    executed_at TIMESTAMPTZ DEFAULT NOW()
);

Redis (Hot Balances and Order Book Cache)

HSET balance:{user_id} BTC_available "1.234" BTC_reserved "0.5"

# Atomic reservation via Lua script:
local avail = tonumber(redis.call('HGET', KEYS[1], ARGV[1]))
if avail >= tonumber(ARGV[2]) then
    redis.call('HINCRBYFLOAT', KEYS[1], ARGV[1], -tonumber(ARGV[2]))
    redis.call('HINCRBYFLOAT', KEYS[1], ARGV[3], tonumber(ARGV[2]))
    return 1
end
return 0

# Order book snapshot cache
SET orderbook:BTC_USDT '{bids:[...],asks:[...]}' EX 1

Fault Tolerance

Exchange Hack Prevention

1. Hot wallet = 5% of assets → limits blast radius
2. Multi-sig: hot=2-of-3, cold=3-of-5 → no single key compromise
3. HSM (Hardware Security Module) → keys never in software memory
4. Withdrawal anomaly detection → auto-freeze + human review
5. IP whitelisting for withdrawal addresses (user-configurable)
6. 24h withdrawal lock after password change
7. Circuit breaker: unusual trading volume → halt market
8. Proof of Reserves: Merkle tree of all balances → public audit

Additional Considerations

Proof of Reserves

A cryptographic Merkle tree aggregates account balances, where each leaf represents a hash of the user identifier and account balance. The root hash is published periodically, allowing users to verify that their balance is included in the tree without revealing other users' balances to the public. An independent auditor then confirms that total on-chain assets held in cold and hot wallets equal or exceed the Merkle root total.

Market Manipulation Detection

Wash trading occurs when the same entity buys and sells against themselves, which the system detects through IP, device fingerprint, and execution timing correlation. Spoofing occurs when large orders are placed and canceled quickly, detected via order lifetime analysis. A circuit breaker automatically halts market trading for a 5-minute cooldown whenever the price moves more than 10% within a 5-minute window.

Comparison with a Traditional Stock Exchange

Compared to a traditional Stock Exchange, a cryptocurrency exchange operates under fundamentally different settlement and custody models. Stock exchanges operate in heavily regulated environments with T+1 or T+2 settlement and central clearing houses (such as the DTCC) holding underlying assets, eliminating direct brokerage custody risk. In contrast, cryptocurrency exchanges manage direct self-custody or user keys, execute instant internal settlement, and support 24/7/365 multi-currency operations alongside on-chain deposits and withdrawals. Because the exchange holds private keys, hack prevention requires a strict hot and cold wallet architecture.

Interview Walkthrough

  • 25-minute cut

    Skip arch50/arch75 depth unless staff.

    • In-memory matching engine per pair + WAL (8 min)
    • Hot wallet ~5% of assets, and cold storage for the rest (9 min)
    • Kafka bridges match to async settlement (8 min)
  • Split the hot wallet (online trading) from the cold wallet (majority of funds offline) to establish security architecture first.
  • Explain the order matching engine as single-threaded per trading pair with an in-memory order book.
  • Cover the deposit and withdrawal pipeline with blockchain confirmation thresholds and ledger reconciliation.
  • Discuss KYC and AML as gating steps before account activation, not inline on every trade.
  • Mention idempotent order placement and balance locking during open orders.
  • Common pitfall: keeping all funds in a hot wallet, where a single breach drains everything, making cold storage mandatory.

Engineering Trade-offs

Exchanges balance matching latency against settlement safety, contrasting fast in-memory books with a durable database-backed audit trail.

Matching Engine: In-Memory vs Database-Backed

Option 1: Database-backed (naive)
  Order book in PostgreSQL: SELECT top bid, ask WHERE bid >= ask
  ✓ Durable (ACID)
  ✗ Throughput: 10K TPS max (DB lock contention)
  ✗ Latency: 5-20ms per match

Option 2: In-memory matching engine (Binance, Kraken)
  Bids: max-heap by price DESC. Asks: min-heap by price ASC.
  ✓ Microsecond latency, 1M+ orders/sec
  → State lost on crash
  Mitigation: WAL to Kafka + periodic snapshots to S3

Option 3: Hybrid (production-grade)
  In-memory for active order book
  PostgreSQL for trade records, order history
  Kafka as durable event log (source of truth for recovery)

Order Types Implementation

Market Order: Match immediately against best available asks.

Limit Order: If matching ask exists, execute; otherwise add to book.

Stop-Loss: Stored in separate stop order book; when triggered, converted to market order.

Regulatory Compliance: KYC/AML Architecture

KYC levels determine trading limits. AML: rules-based (structuring, pass-through, velocity) + ML layer (transaction graph analysis, pattern detection, network clustering). Suspicious Activity Report (SAR) filed with FinCEN. Travel Rule (FATF): share sender/receiver KYC for transfers > $3,000.

💬Review

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...