Designing a Cryptocurrency Exchange – Accurate Auditable Balances at Scale

Designing a Cryptocurrency Exchange

Designing a Cryptocurrency Exchange — Accurate, Auditable Balances at Scale

A system design deep dive into building an exchange that maintains accurate, auditable balances while processing thousands of trades per second, scaled to millions of requests per minute — combining a stock-exchange-grade matching engine with a bank-grade custody and ledger layer.

01

Introduction and History

A cryptocurrency exchange looks, from the outside, like a stock trading app — you place a buy or sell order, it gets matched, and your balance updates. But underneath, it is solving a much harder problem than a normal trading platform: it must be simultaneously a trading system (fast order matching) and a bank (custody of real digital assets, deposits and withdrawals to public blockchains, and money that can never simply be “wrong”).

Unlike a traditional stock exchange, where a central clearinghouse and regulated custodians handle settlement over days, a crypto exchange typically holds customer funds directly and must track exactly how much of each cryptocurrency every single user owns, at every single moment, with zero room for a rounding error or a lost cent — because unlike a typical web app, “wrong” here means real, often irreversible, financial loss.

1.1 A short history

Early cryptocurrency exchanges in the early 2010s were simple: a basic web server, a SQL database tracking balances, and a naive matching algorithm. As trading volume grew, exchanges rebuilt around dedicated matching engines borrowed from traditional finance, while adding an entirely new layer that stock exchanges never needed: managing actual on-chain wallets, watching blockchains for incoming deposits, and safely broadcasting outgoing withdrawal transactions. Several early exchanges suffered catastrophic losses from weak custody practices and sloppy balance tracking — these failures are why the modern architecture below treats the ledger and wallet layers as being just as critical as the matching engine itself.

Analogy

Think of a crypto exchange as running two businesses under one roof at the same time: a stock exchange (matching buyers and sellers fast) and a bank vault (safely custodying real money that can be stolen if mishandled). Most trading platforms only need to be good at the first job. A crypto exchange has to be excellent at both, and the two jobs constantly have to agree with each other down to the last decimal.

This tutorial walks through both halves of that problem in depth: the fast, familiar trading half (a matching engine much like the one powering any traditional stock exchange), and the custody-and-correctness half that makes crypto exchanges uniquely demanding — a double-entry ledger that can prove, mathematically, that no money was ever created or destroyed, and a wallet architecture that keeps real on-chain assets safe from both external attackers and internal mistakes.

02

Problem and Motivation

Let’s break down what “accurate, auditable balances while processing thousands of trades per second” actually demands.

Requirement 1

Balances must always be correct

If a user has 1.5 BTC, the system must always say 1.5 BTC — not 1.50000001, not 1.49999999. Every trade, deposit, withdrawal, and fee must update balances in a way that can never silently drift from reality, even under concurrent trading load from thousands of users simultaneously.

Requirement 2

Every balance change must be auditable

Regulators, auditors, and the exchange’s own risk team must be able to answer “why does this user have exactly this balance right now?” by reconstructing the complete history of every trade, deposit, and withdrawal that led to it — with nothing hidden, deleted, or silently overwritten.

Requirement 3

High throughput

Popular trading pairs (like BTC/USDT) can see thousands of trades per second during volatile markets, and the “millions of requests per minute” scenario means the ingress layer alone needs to comfortably handle on the order of 50,000+ requests per second sustained, with much higher bursts during major price moves.

Requirement 4

Funds must never be double-spent or lost

Because crypto exchanges custody real assets, a balance tracking bug isn’t just an inconvenience — it can mean a user withdraws funds they don’t actually have, or the exchange becomes insolvent without realizing it. This requirement drives one of the most important design decisions in this entire tutorial: using double-entry bookkeeping for every single balance change.

Requirement 5

Custody of real on-chain assets

Unlike a stock exchange, where the “shares” being traded are entries in a centrally-managed securities depository, a crypto exchange typically holds the actual private keys controlling real cryptocurrency on public blockchains. If a private key is stolen, the funds it controls can often be moved instantly and irreversibly, with no central authority able to freeze or reverse the transaction. This single fact is the reason the hot/cold wallet architecture and multi-signature approval process described later exist at all.

Requirement 6

Regulatory compliance

Most jurisdictions that license cryptocurrency exchanges require Know Your Customer (KYC) identity verification and Anti-Money Laundering (AML) transaction monitoring, similar to traditional banks. Every account needs a verified identity before significant trading or withdrawal activity is allowed, and every transaction needs to be screened against known risk patterns and, in many jurisdictions, sanctions lists.

🚨
Production example — Mt. Gox (2014)

In 2014, a major Japan-based exchange called Mt. Gox collapsed after losing roughly 850,000 bitcoins, worth hundreds of millions of dollars at the time, due to a combination of weak security practices and — critically — balance tracking and reconciliation failures that meant the exchange did not actually know how much cryptocurrency it truly held versus what its internal ledger claimed. This remains the canonical case study in why the ledger and wallet reconciliation systems described in this tutorial are treated as safety-critical infrastructure, not optional accounting features.

💬
What an interviewer may ask

“Why can’t you just use a simple ‘balance’ column in a users table and update it directly on every trade?” The answer: a single mutable balance column gives you no audit trail, is prone to race conditions under concurrent updates, and gives you no mathematical guarantee that money wasn’t created or destroyed by a bug. Double-entry bookkeeping, where every transaction is recorded as a balanced set of debits and credits, gives you both an audit trail and a built-in correctness check.

03

Architecture and Components

Now let’s design the full system, box by box, including the load balancer and API gateway the platform needs at its edge, plus the ledger and wallet layers unique to a crypto exchange.

Client Layer Retail Trading App (Mobile/Web) Institutional (REST + WebSocket) Trading Bot (Algorithmic API) Edge & Ingress Load Balancer (L4 TCP, health checks) API Gateway (authN, rate limit, validation) Trading Layer Order Management Servicevalidation, enrichmentKYC coordination Balance Reservation Serviceoptimistic lockinglocks funds pre-match Matching Enginesymbol-sharded order booksprice-time priority Ledger & Wallet Layer Ledger Servicedouble-entry bookkeepingbalanced invariant Wallet Servicehot & cold walletsmultisig for cold Blockchain Node Gatewaydeposit watcherwithdrawal broadcaster Compliance Layer KYC / AML Service Reconciliation Service Storage Layer Write-Ahead Logappend-only durablecrash recovery Ledger Databaseimmutable entriesACID transactions In-memory Cachebalance snapshotorder book Message Queuedurable event streamasync settlement Cross-Cutting Monitoring Service (metrics + logs + traces)
Fig 3.1 — End-to-end architecture of the exchange, from client to storage, with every box labeled by its exact role.

3.1 Component-by-component breakdown

Edge

Load Balancer

Sits at the front of the system, distributing inbound connections across regions and gateway nodes using Layer 4 TCP routing with fast health checks, so a failing node is pulled out of rotation within milliseconds without any client-visible disruption.

Edge

API Gateway

Single entry point for all client traffic. Handles authentication (API keys, session tokens, two-factor verification for sensitive actions), authorization (which accounts can trade or withdraw), rate limiting, and request validation before anything reaches the trading or ledger layers.

Trading

Order Management Service (OMS)

Receives the validated order, enriches it with account and instrument metadata, and coordinates with the KYC/AML service and Balance Reservation Service before the order is allowed anywhere near the matching engine.

Trading

Balance Reservation Service

A crypto-exchange-specific component with no direct equivalent in most other trading systems. Before an order can be accepted, this service locks (reserves) the required funds — for example, locking the USDT needed for a buy order — so that a user cannot place two orders that together spend more than they actually own. The lock releases only when the trade settles or the order is cancelled.

Trading

Matching Engine

Matches buy and sell orders using price-time priority, exactly as in traditional trading systems, sharded by trading pair so each pair’s order book is owned by a single, fast, contention-free process.

Ledger

Ledger Service

True source of truth for every user’s balance. Applies every balance-affecting event — trades, deposits, withdrawals, fees — as a double-entry transaction, where every single change is recorded as a matched pair (or set) of debits and credits that must always sum to zero.

Custody

Wallet Service

Manages the exchange’s actual on-chain cryptocurrency holdings, coordinating between hot wallets (small, online, fast for withdrawals) and cold wallets (large, offline, safe from remote attackers). Works closely with the Blockchain Node Gateway to detect deposits and safely broadcast withdrawals.

Custody

Blockchain Node Gateway

Runs or connects to full nodes for each supported blockchain, watching for incoming deposit transactions (crediting a user’s balance only after enough confirmations) and broadcasting outgoing withdrawal transactions once approved.

Compliance

KYC / AML Service

Screens users and transactions for identity verification (Know Your Customer) and suspicious activity (Anti-Money Laundering) — a regulatory requirement in virtually every jurisdiction that licenses crypto exchanges.

Compliance

Reconciliation Service

Continuously compares the Ledger Service’s recorded balances against the Wallet Service’s actual on-chain holdings, alerting immediately if they ever diverge beyond an acceptable tolerance — the automated safeguard against a Mt. Gox-style silent balance drift.

Storage

WAL, Ledger DB, Cache, MQ

The write-ahead log durably records every order and trade event before acknowledgment. The ledger database stores the permanent, immutable double-entry record of every balance change. The cache serves fast balance and order book reads without touching the ledger’s write path. The message queue decouples the matching engine from the ledger, so a temporarily slow ledger write never blocks order matching itself.

Ops

Monitoring Service

Collects metrics, logs, and traces from every component, with particular attention to balance-related anomalies given the financial stakes involved.

Analogy for the load balancer

Like the host at a busy restaurant, the load balancer looks at which “kitchen” (gateway instance) is least busy right now and sends the next customer there, without the customer ever seeing the decision being made.

💬
What an interviewer may ask

“Why have a separate Balance Reservation Service instead of just checking the balance directly in the Ledger Service when an order is placed?” A strong answer: reservation needs to be extremely fast and happen before the order reaches the matching engine, while the ledger’s authoritative balance update only happens after a trade actually settles — separating “can this order be accepted” (a fast, optimistic lock) from “record this trade permanently” (a slower, durable, auditable write) keeps the hot trading path fast while keeping the ledger rigorous.

04

Internal Working

The heart of this system’s correctness guarantee is double-entry bookkeeping — a centuries-old accounting technique, originally used by merchants long before computers existed, adapted here to guarantee that a software bug can never silently create or destroy money.

4.1 How double-entry bookkeeping works

Every balance-affecting event is recorded as a set of entries where the total of all debits exactly equals the total of all credits. If they ever don’t match, the system has a bug — and this can be checked programmatically after every single transaction, giving an automatic, continuous correctness proof.

Trade ExecutedBuyer gets 0.5 BTC / Seller gets 20,000 USDT Buyer Account Entries Debit USDT AvailableAmount 20,000 Credit BTC AvailableAmount 0.5 Seller Account Entries Debit BTC AvailableAmount 0.5 Credit USDT AvailableAmount 20,000 Exchange Fee Entries Credit Fee RevenueAmount = trading fee Invariant CheckSum of all debits == sum of all credits (per asset)fails loudly if unbalanced — runs before commit
Fig 4.1 — A single trade produces a balanced set of debit and credit entries across buyer, seller, and exchange fee accounts.
Analogy

Think of double-entry bookkeeping like a see-saw that must always stay perfectly level. Every time money moves anywhere in the system, something is added to one side and removed from the other, in exactly equal amounts. If the see-saw ever tips even slightly, you know immediately — without waiting for a customer complaint or a manual audit — that something in the system is broken.

4.2 Simplified Java example — recording a trade as a ledger transaction

LedgerTransaction.java & LedgerService.java — balanced-books enforcement
public class LedgerTransaction {
    private final String transactionId;
    private final List<LedgerEntry> entries = new ArrayList<>();

    public void addEntry(String accountId, String asset, long amount, EntryType type) {
        entries.add(new LedgerEntry(accountId, asset, amount, type));
    }

    // Must be called before this transaction is allowed to commit
    public void validateBalanced() {
        Map<String, Long> netByAsset = new HashMap<>();
        for (LedgerEntry e : entries) {
            long signed = e.type() == EntryType.DEBIT ? e.amount() : -e.amount();
            netByAsset.merge(e.asset(), signed, Long::sum);
        }
        for (Map.Entry<String, Long> net : netByAsset.entrySet()) {
            if (net.getValue() != 0) {
                throw new IllegalStateException(
                    "Unbalanced ledger transaction for asset " + net.getKey());
            }
        }
    }
}

public class LedgerService {
    public void applyTrade(Trade trade) {
        LedgerTransaction txn = new LedgerTransaction(trade.id());

        txn.addEntry(trade.buyerId(), trade.quoteAsset(), trade.notional(), EntryType.DEBIT);
        txn.addEntry(trade.buyerId(), trade.baseAsset(), trade.quantity(), EntryType.CREDIT);

        txn.addEntry(trade.sellerId(), trade.baseAsset(), trade.quantity(), EntryType.DEBIT);
        txn.addEntry(trade.sellerId(), trade.quoteAsset(), trade.notional(), EntryType.CREDIT);

        txn.addEntry(EXCHANGE_FEE_ACCOUNT, trade.quoteAsset(), trade.feeAmount(), EntryType.CREDIT);

        txn.validateBalanced();  // fails loudly instead of silently corrupting balances
        ledgerRepository.commitAtomically(txn); // single atomic database transaction
    }
}

Notice the critical safety property: validateBalanced() runs before anything is written to the database, and the entire set of entries is committed as a single atomic database transaction. If any single entry fails to apply, none of them apply — there is no possible intermediate state where money exists on one side of a trade but not the other.

4.3 Fixed-point arithmetic for amounts

Just like a stock exchange’s prices, all cryptocurrency amounts must be represented as fixed-point integers (e.g., satoshis for Bitcoin, the smallest indivisible unit for each asset) rather than floating-point numbers. Floating-point rounding errors are exactly the kind of subtle bug that double-entry bookkeeping is designed to catch — but it’s far better to prevent the error entirely by never using floating-point representation for money in the first place.

⚠️
Common mistake

Using a floating-point double to store cryptocurrency amounts. Two floating-point additions that should mathematically cancel out can leave a tiny non-zero residue, which — multiplied across millions of trades per day — can eventually cause the balanced-books invariant to fail in ways that are maddening to debug. Always use integer amounts in the smallest unit of the asset.

4.4 Optimistic locking for balance reservation

Because thousands of users may be trading concurrently, the Balance Reservation Service uses optimistic concurrency control (a version number or compare-and-swap check on each account’s available balance) rather than heavyweight database locks, so that reserving funds for one user’s order never blocks another unrelated user’s order from being processed.

💬
What an interviewer may ask

“What happens if two orders from the same user try to reserve overlapping funds at nearly the same instant?” A good answer: the Balance Reservation Service must serialize reservation checks per account (not per trading pair), typically using a per-account version counter with compare-and-swap, so the second request either succeeds against the updated available balance or is correctly rejected as insufficient funds — never allowing both to succeed and overspend.

4.5 The matching engine itself

The matching engine used by a crypto exchange is architecturally almost identical to a traditional stock exchange’s matching engine: a single-threaded event loop per trading pair, maintaining an in-memory order book with price-time priority, and producing trade events for downstream settlement. This shared lineage is why so many crypto exchange matching engines were built by engineers who came directly from traditional finance trading system backgrounds.

CryptoOrderBook.java — single-threaded, lock-free matching loop
public class CryptoOrderBook {
    private final TreeMap<Long, ArrayDeque<Order>> bids =
        new TreeMap<>(Comparator.reverseOrder());
    private final TreeMap<Long, ArrayDeque<Order>> asks = new TreeMap<>();

    // Runs on the single dedicated thread for this trading pair only
    public List<Trade> submit(Order incoming) {
        List<Trade> trades = new ArrayList<>();
        TreeMap<Long, ArrayDeque<Order>> opposite =
            incoming.isBuy() ? asks : bids;

        while (incoming.remainingQty() > 0 && !opposite.isEmpty()) {
            Map.Entry<Long, ArrayDeque<Order>> bestLevel = opposite.firstEntry();
            long bestPrice = bestLevel.getKey();
            boolean crosses = incoming.isBuy()
                ? incoming.limitPrice() >= bestPrice
                : incoming.limitPrice() <= bestPrice;
            if (!crosses) break;

            ArrayDeque<Order> queue = bestLevel.getValue();
            Order resting = queue.peekFirst();
            long fillQty = Math.min(incoming.remainingQty(), resting.remainingQty());

            long fee = computeFee(fillQty, bestPrice, incoming.feeTier());
            trades.add(new Trade(incoming.id(), resting.id(), bestPrice, fillQty, fee));

            incoming.reduceQty(fillQty);
            resting.reduceQty(fillQty);
            if (resting.remainingQty() == 0) queue.pollFirst();
            if (queue.isEmpty()) opposite.remove(bestPrice);
        }

        if (incoming.remainingQty() > 0) {
            TreeMap<Long, ArrayDeque<Order>> same =
                incoming.isBuy() ? bids : asks;
            same.computeIfAbsent(incoming.limitPrice(), k -> new ArrayDeque<>())
                .addLast(incoming);
        }
        return trades;
    }

    private long computeFee(long qty, long price, FeeTier tier) {
        // Maker/taker fee schedules are common: lower fees for resting
        // liquidity providers, slightly higher fees for aggressive takers.
        return (qty * price * tier.takerBps()) / 10_000L;
    }
}

Notice the fee calculation happens right at match time and flows directly into the trade record, which the Ledger Service then converts into the exchange fee credit entry shown in Fig 4.1. Just like the pure trading example, no locks appear anywhere in this loop — correctness comes entirely from having exactly one thread own this trading pair’s order book.

4.6 Maker-taker fee models

Most crypto exchanges use a maker-taker fee model to encourage liquidity: an order that rests in the book and provides liquidity (a “maker”) pays a lower fee than an order that immediately matches against existing liquidity (a “taker”). This fee logic lives entirely inside the matching engine’s trade generation step, keeping fee computation as fast and deterministic as the matching decision itself, rather than being calculated later during ledger settlement.

4.7 Order book data structure choices

The same price-level-map-plus-FIFO-queue structure used in traditional trading systems applies here: a sorted structure (balanced tree, skip list, or bucketed array for common tick sizes) keyed by price, with a time-ordered queue of orders at each level. Crypto markets add one wrinkle traditional equity markets rarely need to worry about at the matching engine level: extremely fine-grained decimal precision, since some assets trade in units far smaller than a traditional stock’s cent-based tick size. This is handled by choosing an appropriately scaled fixed-point integer representation per asset (for example, representing Bitcoin amounts in satoshis, the smallest indivisible unit) rather than trying to use one universal precision across every asset.

4.8 Self-trade prevention

Because many crypto traders run automated market-making bots that place both buy and sell orders on the same pair simultaneously, self-trade prevention becomes especially important: the matching engine checks whether a resting order and an incoming order belong to the same account before matching them, and if so, either cancels the smaller/newer order or skips to the next price level, depending on the configured self-trade prevention mode. Without this, a market maker’s own bot could unintentionally trade against itself, generating fees and confusing position tracking for no economic benefit.

05

Data Flow and Lifecycle

Let’s trace one order from submission all the way through to a settled, ledger-recorded trade.

Client Load Bal. Gateway OMS Balance Matching Msg Queue Ledger submit order: Buy 0.5 BTC route to healthy node auth + rate limit validated request reserve USDT balance balance reserved & locked submit to order book match against book publish trade event deliver trade event apply double-entry release lock, confirm settlement execution report order filled notification
Fig 5.1 — The full lifecycle of a single order, from submission through matching to final ledger settlement.

5.1 Order and balance states

StateMeaning
NEWOrder received, funds not yet reserved
RESERVEDRequired funds locked, order live on the book
PARTIALLY_FILLEDSome quantity matched and settled in the ledger, remainder still resting
FILLEDFully matched and settled, reservation fully released
CANCELLEDWithdrawn before full fill, remaining reservation released back to available balance
REJECTEDFailed validation, risk, or KYC check, no funds ever reserved
💡
Beginner example

Say you have 25,000 USDT available and place an order to buy 0.5 BTC at 20,000 USDT. The Balance Reservation Service immediately locks 10,000 USDT, leaving 15,000 USDT available for other orders. If your order fully fills, the Ledger Service converts that 10,000 USDT lock into an actual settled trade — debiting USDT and crediting BTC. If you cancel first, the 10,000 USDT lock is simply released back to your available balance, untouched.

5.2 Deposit lifecycle

Deposits follow a different, blockchain-driven lifecycle:

  1. A user sends cryptocurrency to a unique deposit address generated for their account by the Wallet Service.
  2. The Blockchain Node Gateway detects the incoming transaction as soon as it appears in the blockchain’s mempool (unconfirmed) and marks it PENDING — visible to the user, but not yet spendable.
  3. Once the transaction accumulates the required number of confirmations (varies by asset — Bitcoin typically requires several confirmations, while some faster chains require fewer), the Ledger Service credits the user’s available balance via a balanced double-entry transaction.
  4. The deposit is now CONFIRMED and fully usable for trading or withdrawal.

5.3 Withdrawal lifecycle

  1. User submits a withdrawal request specifying asset, amount, and destination address.
  2. The Withdrawal Risk Service evaluates the request (address allow-listing, daily limits, large-amount review) as detailed in the Security section.
  3. If approved automatically, the Wallet Service processes the request — small withdrawals broadcast directly from the hot wallet; larger withdrawals queue for multi-signature approval from the cold wallet.
  4. Once signed and broadcast, the Blockchain Node Gateway monitors the transaction until it reaches enough confirmations to be considered final.
  5. The Ledger Service records the withdrawal as a debit against the user’s balance, balanced by a credit removing the corresponding amount from the exchange’s wallet-tracking account.
06

Advantages, Disadvantages and Trade-offs

Every architectural decision here involves giving something up in exchange for something else. The table below makes these trade-offs explicit — the kind of reasoning worth walking through out loud in a system design interview, since interviewers usually care more about why a decision was made than the decision itself. As with the matching engine design borrowed from traditional finance, there is no single version of this system that is simultaneously the fastest, the cheapest, and the simplest to operate; the goal is choosing the right trade-off for each layer given what that layer actually needs to guarantee.

Upside

Advantages of double-entry ledger design

  • Built-in, continuous correctness check — an unbalanced transaction fails loudly instead of silently corrupting balances.
  • Complete, immutable audit trail satisfying regulatory and forensic requirements.
  • Clean separation between fast trading-path reservation and durable ledger settlement.
  • Reconciliation against on-chain wallet balances becomes a simple sum-and-compare operation.
Downside

Disadvantages and challenges

  • Every balance change requires writing multiple entries, not a single row update — more storage and write overhead.
  • Requires careful transaction design so entries for one trade are never partially applied.
  • Reservation and settlement being separate steps adds a small amount of end-to-end latency compared to a naive single-balance update.
  • Cross-asset consistency (e.g., ensuring BTC and USDT ledgers both update correctly for one trade) requires atomic multi-row transactions, which can be harder to scale than single-row updates.

6.1 Key trade-offs

DecisionOption AOption BWhat we chose & why
Balance modelSingle mutable balance columnDouble-entry ledger with immutable entriesDouble-entry — auditability and built-in correctness checking outweigh the extra write cost
Fund lockingCheck balance at settlement time onlyReserve funds at order acceptance timeReserve upfront — prevents users from overspending across multiple simultaneous orders
Wallet custodyAll funds in one hot walletHot/cold wallet split with multisigHot/cold split — limits maximum loss if the hot wallet is ever compromised
Settlement timingSynchronous ledger write in the matching hot pathAsynchronous via message queueAsynchronous — keeps matching engine fast while ledger still settles within milliseconds
Deposit creditingCredit immediately on first sight in mempoolCredit only after N confirmationsWait for confirmations — protects against blockchain reorganizations reversing an already-credited deposit
07

Performance and Scalability

Let’s size the system for the stated scenario: thousands of trades per second, with an overall request scenario of millions of requests per minute.

7.1 Sizing the problem

  • Assume 3,000,000 requests per minute at peak (orders, cancels, balance queries) = 50,000 requests/second average.
  • Real trading volume is bursty — a sharp price move can cause a 5–10x spike in seconds, so design for 250,000–500,000 requests/second at brief peaks.
  • Of these, only a fraction are actual trade executions — thousands of trades per second on the busiest pairs (like BTC/USDT), well within a single-symbol matching engine shard’s capacity.

7.2 Where the time goes

StageTarget latency
Load Balancer + API Gateway< 150 microseconds
KYC/risk pre-checks (cached decision)< 50 microseconds
Balance reservation (optimistic lock)< 100 microseconds
Matching engine core match< 50 microseconds
Ledger settlement (async, via queue)< 5 milliseconds
Total order acceptance latency< 500 microseconds

Note that ledger settlement is allowed a slightly larger budget than pure order matching, because it happens asynchronously after the trade is already matched — the user sees their order fill quickly, while the durable, auditable balance record catches up within single-digit milliseconds.

7.3 Scaling techniques

Technique

Sharding by trading pair

Just like a stock exchange shards by symbol, a crypto exchange shards its matching engines by trading pair (BTC/USDT, ETH/USDT, and so on), so the busiest pairs get dedicated capacity without being slowed down by less popular pairs.

Technique

Sharding the ledger by account

The ledger database itself can be partitioned by account ID range or hash, since most transactions only need atomicity within the small set of accounts involved in a single trade (buyer, seller, exchange fee account) — this allows the ledger to scale horizontally while keeping each individual transaction’s atomicity guarantee intact.

Technique

Asynchronous settlement via message queue

Decoupling the matching engine from the ledger via a durable message queue means a temporary slowdown in ledger writes (e.g., during a database failover) never stalls order matching — trades queue up and settle as soon as the ledger catches up, with the write-ahead log ensuring nothing is lost in between.

💬
What an interviewer may ask

“If the ledger settles asynchronously, how do you prevent a user from withdrawing funds from a trade that hasn’t actually settled yet?” A good answer: withdrawal requests must check against the ledger’s confirmed, settled balance — never against an optimistic or in-flight balance — so a withdrawal is only ever approved once the corresponding ledger transaction has actually committed.

7.4 Capacity planning walkthrough

Let’s work through a concrete capacity plan for the stated scenario of millions of requests per minute and thousands of trades per second.

  • Assumption: 3,000,000 requests per minute at peak, spread across roughly 200 actively traded pairs.
  • Average per pair: 3,000,000 / 200 = 15,000 requests per minute per pair on average — roughly 250 per second. Comfortable for a single matching engine shard.
  • Skew assumption: the top 10 pairs (BTC/USDT, ETH/USDT, and similar) might account for 60% of total volume — 1,800,000 requests/minute across 10 pairs = 180,000/minute per hot pair = 3,000/second. This is the “thousands of trades per second” scenario stated in the requirements, and it still fits comfortably within one dedicated core running an optimized single-threaded matching loop.
  • Ledger write throughput: Each trade generates roughly 5 ledger entries (buyer debit/credit, seller debit/credit, fee credit). At 3,000 trades/second on the hottest pair alone, that’s 15,000 ledger entries/second just from that one pair — well within what a properly indexed, partitioned relational database cluster can sustain when writes are batched and committed as grouped transactions rather than one-by-one.

7.5 CAP theorem for the ledger

The ledger database, unlike the matching engine, genuinely faces a classic CAP theorem trade-off, because it must remain strongly consistent (an unbalanced or partially-applied transaction is never acceptable) even during network partitions between replicas. This is why the ledger favors Consistency over Availability during a partition: if the primary ledger node cannot confirm a transaction has been safely replicated, it is far better to briefly reject new trades from settling than to risk two replicas disagreeing about a user’s balance.

Contrast this with the order book cache, which favors Availability over Consistency — it’s acceptable for a balance shown in a UI to be a few hundred milliseconds stale, but never acceptable for the authoritative ledger balance used to approve a withdrawal to be stale or inconsistent.

💬
What an interviewer may ask

“Where would you accept eventual consistency in this system, and where would you never accept it?” A strong answer draws a clear line: order book depth displays, balance caches for UI display, and analytics can all be eventually consistent. The ledger’s authoritative balance, withdrawal approval decisions, and the balanced-books invariant itself must always be strongly consistent — because being wrong there means real financial loss, not just a stale-looking screen.

08

High Availability and Reliability

A crypto exchange going down, or worse, applying incorrect balance updates during a failure, can mean real financial loss for users and the exchange alike.

Ledger Databasesum of all user balances On-Chain Wallet Balanceshot wallet + cold wallet Reconciliation Serviceruns every few minutes Match within tolerance? Yes: log success metricno action needed No: trigger Sev-1 alerthalt withdrawals (circuit breaker) yes no
Fig 8.1 — Continuous reconciliation between the ledger’s recorded balances and actual on-chain wallet holdings, with an automatic circuit breaker if they ever diverge.

8.1 Techniques for high availability

  • Database replication with synchronous commit for the ledger database, ensuring a failover never loses a committed balance transaction.
  • Hot standby matching engines per trading pair shard, replaying the write-ahead log to take over within milliseconds of a detected failure.
  • Continuous reconciliation between the ledger’s total balances and the wallet service’s actual on-chain holdings, catching any drift automatically rather than waiting for a user complaint or manual audit.
  • Automatic withdrawal circuit breakers that halt withdrawals immediately if reconciliation ever detects a mismatch beyond a tiny, expected tolerance (like unconfirmed network fees).
  • Disaster recovery site with geographically separate infrastructure, including secure, separately-stored cold wallet backup material.

8.2 Why reconciliation is non-negotiable

The Mt. Gox collapse mentioned earlier happened, in large part, because there was no reliable automated process continuously checking whether the exchange’s internal ledger matched its actual on-chain holdings. A modern exchange treats this reconciliation loop as one of its most safety-critical pieces of infrastructure — often running every few minutes, with any detected discrepancy immediately halting withdrawals until a human confirms what happened.

⚠️
Common mistake

Running reconciliation only as a nightly batch job. A discrepancy that isn’t caught for many hours gives an attacker or a buggy deployment a long window to drain funds before anyone notices. Reconciliation should run continuously, in near real time, not just once a day.

8.3 RPO and RTO targets

MetricMeaningTypical target
RPO (Recovery Point Objective)How much data can we afford to lose?Zero for ledger transactions — synchronous commit to at least one replica
RTO (Recovery Time Objective)How long can we be down?Single-digit seconds for matching engine failover; minutes for full ledger database failover

8.4 Consensus for failover coordination

Just as in traditional trading system design, the failover controller that decides when to promote a standby matching engine or ledger database replica uses a consensus protocol like Raft, ensuring only one node can ever be treated as primary at a time — preventing a dangerous split-brain scenario where two nodes might independently accept and settle conflicting trades or ledger transactions for the same account.

8.5 Regular failover and reconciliation drills

Beyond testing matching engine failover, a crypto exchange must also regularly rehearse its reconciliation and withdrawal circuit-breaker path — deliberately injecting a controlled, simulated balance mismatch in a staging environment to confirm the alert fires, withdrawals actually halt, and the on-call engineer’s runbook actually works, rather than discovering gaps in that process during a real incident.

8.6 Disaster recovery for custody infrastructure

Disaster recovery for a crypto exchange extends beyond the usual “spin up infrastructure in another region” playbook, because cold wallet key material cannot simply live in a single data center without becoming a single point of catastrophic failure. Production exchanges typically split cold wallet signing key material across multiple geographically separated, physically secured locations using key-splitting schemes (such as Shamir’s Secret Sharing or hardware security modules with distributed multi-party approval), so that no single site’s loss or compromise can either lock the exchange out of its own cold storage or, worse, allow a single compromised location to unilaterally move funds.

09

Security

Security here spans both typical web application concerns and crypto-specific custody risks.

9.1 Authentication and authorization

  • Mandatory two-factor authentication, with additional step-up verification (like email/SMS confirmation) required specifically for withdrawal requests.
  • API key scoping — a trading-only API key should never be able to authorize a withdrawal, even if leaked.
  • Withdrawal address allow-listing, with a mandatory delay before a newly added withdrawal address can actually be used.

9.2 Wallet custody security

Hot Wallet Layer — Online Wallet Servicewithdrawal orchestrationsizing & sweeping Hot Walletsmall balance for fast withdrawalslimits max loss Cold Wallet Layer — Offline Multi-Signature Approvalrequires multiple human signersseparate physical locations Cold Walletmajority of exchange fundsisolated from online systems Blockchain Layer Blockchain Node Gatewaywatches depositsbroadcasts withdrawals Public Blockchain Network small withdrawal large withdrawal needs approval signs & releases broadcast tx via node gateway broadcast to network deposit seen → credit after N confirmations sweep excess to cold
Fig 9.1 — Hot wallets hold only a small operating balance for fast withdrawals; the vast majority of funds sit in multi-signature cold storage, isolated from any online system.
  • Hot/cold wallet split — only a small percentage of total exchange funds sit in internet-connected hot wallets at any time, strictly limiting the maximum possible loss from a hot wallet compromise.
  • Multi-signature approval for cold wallet withdrawals, requiring multiple independent human signers (often in different physical locations) before a large withdrawal can be broadcast.
  • Automated sweeping of excess hot wallet balance into cold storage on a regular schedule, so the hot wallet never accumulates more than its intended operating buffer.

9.3 Deposit confirmation requirements

A deposit is only credited to a user’s ledger balance after a blockchain-specific number of confirmations (blocks mined on top of the deposit transaction), to protect against blockchain reorganizations that could otherwise let an attacker deposit funds, get credited, and then have the deposit transaction disappear from the canonical chain.

9.4 Withdrawal risk controls

  • Velocity limits — maximum withdrawal amount per day/week per account, with larger withdrawals requiring manual review.
  • Anomaly detection — flagging withdrawal patterns that deviate significantly from a user’s normal behavior.
  • AML/sanctions screening on withdrawal destination addresses where technically feasible.

9.5 Cost optimization

Even in a system this focused on correctness and security, cost still matters at scale:

  • Running the matching engine and ledger tiers on dedicated, right-sized infrastructure (since these benefit most from predictable performance), while running elastic, bursty tiers like the API Gateway and KYC screening service on auto-scaling cloud infrastructure that scales down during quiet trading hours.
  • Tiered storage for historical trade and transaction data — recent data in fast, queryable storage, older records moved to cheaper cold storage after a retention window, while still meeting regulatory record-keeping requirements (often 5–7 years depending on jurisdiction).
  • Batching blockchain withdrawal transactions where the underlying network supports it, reducing the per-transaction network fee cost passed on to users or absorbed by the exchange.
  • Running blockchain full nodes for high-volume assets on dedicated infrastructure, while using third-party node providers for lower-volume, less frequently traded assets to avoid the operational cost of running infrastructure for every single supported blockchain.
💬
What an interviewer may ask

“How would you size the hot wallet to balance user experience against security risk?” A thoughtful answer discusses the trade-off directly: too small a hot wallet means frequent, slow cold-to-hot transfers that delay withdrawals; too large a hot wallet increases the maximum loss from a compromise. Production exchanges typically model expected withdrawal volume over a rolling window (e.g., a day) and keep the hot wallet sized just above that, automatically sweeping any excess to cold storage.

9.6 Simplified Java example — withdrawal risk check

WithdrawalRiskService.java — tiered risk decision
public class WithdrawalRiskService {
    public WithdrawalDecision evaluate(WithdrawalRequest request, AccountProfile profile) {
        if (!profile.isAddressAllowListed(request.destinationAddress())) {
            return WithdrawalDecision.reject("Address not on allow list");
        }
        if (profile.addressAddedAt(request.destinationAddress())
                .isAfter(Instant.now().minus(Duration.ofHours(24)))) {
            return WithdrawalDecision.reject("New address cooling period not elapsed");
        }
        long dailyTotal = ledgerService.sumWithdrawalsLast24h(request.accountId(), request.asset());
        if (dailyTotal + request.amount() > profile.dailyWithdrawalLimit(request.asset())) {
            return WithdrawalDecision.requireManualReview("Exceeds daily withdrawal limit");
        }
        if (request.amount() > profile.largeWithdrawalThreshold(request.asset())) {
            return WithdrawalDecision.requireManualReview("Large withdrawal requires review");
        }
        return WithdrawalDecision.approve();
    }
}

Notice how the check escalates in severity: hard rejection for policy violations (unrecognized address), automatic manual review for unusually large amounts, and straightforward approval otherwise — this tiered approach lets the vast majority of legitimate, routine withdrawals process quickly while still catching the higher-risk cases that genuinely warrant a human look.

10

Monitoring, Logging and Metrics

Given the financial stakes, monitoring for a crypto exchange goes beyond typical latency and error-rate metrics.

10.1 Key metrics to track

MetricWhy it matters
Ledger balance invariant violationsShould always be zero — any non-zero count is a critical incident
Reconciliation drift (ledger vs on-chain)Direct early-warning signal for fund safety issues
Order-to-settlement latency (p50, p99, p99.9)Core performance promise of the trading path
Withdrawal approval queue depthDetects operational bottlenecks in multisig approval flow
Hot wallet balance vs thresholdEnsures automated sweeping is functioning correctly
Deposit confirmation lag per blockchainDetects blockchain node issues or network congestion

10.2 Service Level Objectives (SLOs)

SLOTarget
Order acceptance latency, p99Under 1 millisecond
Trade settlement latency (order to ledger commit), p99Under 10 milliseconds
Reconciliation check frequencyEvery 1–5 minutes, continuously
Ledger balance invariant violationsZero, always — treated as a Sev-1 incident if ever non-zero

10.3 Approach

  • Every ledger transaction’s balanced-books check result is emitted as a metric, not just logged, so a dashboard can show a real-time count of invariant violations (which should always read zero).
  • Distributed tracing with correlation IDs across the order-to-settlement path, so a slow or failed trade can be diagnosed stage by stage.
  • Async, non-blocking metrics emission from the matching engine’s hot path, exactly as in traditional trading systems, so monitoring itself never adds latency.
  • Automated alerting wired directly to the withdrawal circuit breaker, so a detected reconciliation mismatch stops withdrawals before a human even has time to react.
⚠️
Common mistake

Treating balance invariant violations as “just another log line” instead of a paged, Sev-1 alert. Any unbalanced ledger transaction indicates a bug that could be actively losing or creating money — this must interrupt an on-call engineer immediately, not wait to be noticed during a routine log review.

10.4 Distributed tracing example

For the order-to-settlement path, a trace reconstructed from correlation-ID-tagged logs might look like this:

Per-stage latency trace for a single order
trace_id=a91f3c order_id=CL-2026-51029
  [gateway]      auth+validate        : 95us
  [oms]          enrich+kyc-check     : 48us
  [balance-svc]  reserve-funds        : 88us
  [matching]     match-and-fill       : 41us
  [queue]        publish-trade-event  : 22us
  [ledger]       apply-double-entry   : 3.1ms
  [ledger]       balance-check-passed : 0.2ms
  ------------------------------------------
  total order-to-settlement latency    : 3.6ms

Having this per-stage breakdown readily available (sampled for a subset of orders to control overhead) is invaluable when diagnosing exactly where latency creeps in — in this example, the ledger’s double-entry write dominates the total, which is expected and acceptable since it happens asynchronously after the user already sees their order filled.

11

Deployment and Cloud

11.1 Deployment practices

  • Blue-green deployment for the API Gateway, OMS, and other stateless services, enabling zero-downtime releases with instant rollback.
  • Extremely cautious, shadow-tested deployment for the Ledger Service and Matching Engine — changes here are tested against mirrored production traffic in a shadow environment, comparing every resulting balance change byte-for-byte against the current production system before going live.
  • Separate, heavily restricted deployment pipeline for Wallet Service code, often requiring multiple independent approvals given its direct access to fund movement logic.
  • Infrastructure as Code for reproducible, auditable environments across development, staging, and disaster recovery sites.

11.2 Cloud vs dedicated infrastructure

Many exchanges run their matching engine and ledger tiers on dedicated, right-sized infrastructure for predictable low-latency performance, while running elastic, bursty tiers (API Gateway, OMS, KYC screening) on auto-scaling cloud infrastructure. Blockchain node infrastructure is often run on dedicated, geographically distributed servers for reliability, since losing connectivity to a blockchain node means missing deposit detection entirely during that outage window.

🚨
Production example

The Mt. Gox collapse discussed earlier is also a cautionary tale about deployment and change-management discipline — reports following the collapse pointed to years of accumulated technical debt and insufficiently rigorous processes around code changes that touched balance-critical systems. This is precisely why this tutorial recommends shadow testing and multiple independent approvals specifically for any code touching the ledger or wallet layers.

11.3 Shadow testing for ledger and wallet changes

Before any change to the Ledger Service or Wallet Service reaches production, it runs in a shadow environment that receives a mirrored copy of real production events (trades, deposits, withdrawal requests) without ever actually moving real funds. The resulting balance changes are compared, entry for entry, against what the current production system produces for the same input. Any divergence — even a single satoshi — is investigated and resolved before the change is allowed anywhere near real customer funds.

💡
Key insight

Treat changes to the Ledger Service and Wallet Service with the same rigor as changes to a bank’s core transaction processing system — extensive automated regression testing, mandatory shadow-mode verification against real traffic patterns, staged rollout, and a well-rehearsed rollback plan for every single change, no matter how small it seems.

12

Databases, Caching and Load Balancing

12.1 Database choices

Data typeStorage choiceWhy
Live order bookIn-memory data structuresNeeds microsecond-level access for matching
Ledger transactions (balances)Relational database with strong ACID guaranteesDouble-entry transactions require atomicity — all entries commit together or none do
Trade and deposit/withdrawal historyTime-series or append-only databaseHigh-write, timestamp-indexed, rarely updated after creation
Write-ahead logPurpose-built durable append-only logFast sequential writes with strong durability for crash recovery

12.2 Why the ledger needs strong ACID guarantees

Unlike some other parts of a large system that can tolerate eventual consistency, the ledger database cannot: a double-entry transaction must be atomic (all entries commit or none do), consistent (the balanced-books invariant always holds), isolated (concurrent transactions don’t see each other’s partial updates), and durable (once committed, never lost). This is exactly what traditional relational databases with proper transaction isolation levels are built for, which is why the ledger typically stays on a strongly consistent relational database even as other parts of the system embrace eventual consistency for performance.

12.3 Partitioning strategy for the ledger database

Partitioning the ledger by account ID (using a hash-based scheme) works well because almost every ledger transaction only involves a small, fixed set of accounts — typically two trading accounts plus the exchange’s own fee account. As long as a single database transaction can span the specific partitions holding those particular accounts (achievable through careful shard co-location of frequently-paired accounts, or a two-phase commit protocol for cross-partition transactions), the system scales horizontally without sacrificing the atomicity that double-entry bookkeeping depends on.

The exchange fee account deserves special mention here: because nearly every trade touches it, it can become a hot partition under high load. A common mitigation is to maintain several fee sub-accounts (round-robining trades across them) and periodically consolidate them into a single reporting balance — trading a small amount of bookkeeping complexity for significantly reduced write contention on this one especially busy account.

12.4 Caching strategy

A read-only cache, updated asynchronously from the ledger’s committed transaction stream, serves fast “what’s my current balance” queries without hitting the ledger database directly for every single read — since balance-check reads vastly outnumber actual balance-changing writes.

12.5 Load balancing approach

Beyond the entry-point load balancer, internal routing sends orders to the correct matching engine shard using consistent hashing on the trading pair, exactly as covered for the order-matching path, ensuring every order for a given pair reaches the same shard in submission order.

13

APIs and Microservices

The platform exposes multiple interfaces to different types of clients and integrations:

  • REST/WebSocket API — used by retail trading apps and most third-party integrations for order submission, balance queries, and real-time trade/order updates.
  • FIX Protocol — offered by many larger exchanges for institutional clients and market makers who need the same low-latency, industry-standard connectivity used in traditional finance.
  • Webhook notifications — for deposit confirmations and withdrawal status updates, so integrating platforms don’t need to poll.

13.1 Microservices boundaries

  • OMS never writes directly to the ledger — it only ever talks to the Balance Reservation Service and Matching Engine through their narrow, well-defined interfaces.
  • Wallet Service is the only component with any blockchain private key access — no other service, including the Ledger Service, ever touches wallet signing material directly.
  • KYC/AML Service is stateless per-request but backed by a fast, replicated identity and risk-status cache, so it doesn’t become a bottleneck in the order acceptance path.

13.2 Example — balance query API response

GET /api/v1/balances/BTC — auditable balance snapshot
GET /api/v1/balances/BTC

{
  "asset": "BTC",
  "available": "1.50000000",
  "reserved": "0.25000000",
  "total": "1.75000000",
  "as_of_ledger_sequence": 9182734021
}

Note the explicit reserved field — showing the user exactly how much is currently locked in open orders — and the as_of_ledger_sequence field, which ties this balance snapshot to an exact, auditable point in the ledger’s history rather than an ambiguous “current” balance.

13.3 Example — deposit confirmation webhook

POST /webhooks/deposit — idempotent deposit notification
POST https://client-integration.example.com/webhooks/deposit
{
  "event": "deposit.confirmed",
  "account_id": "acct_88213",
  "asset": "ETH",
  "amount": "2.35000000",
  "tx_hash": "0x7f9a2e...",
  "confirmations": 32,
  "idempotency_key": "dep_0x7f9a2e_credit"
}

The idempotency_key is derived directly from the on-chain transaction hash, so if the webhook delivery is retried (a common and expected occurrence in distributed systems), the receiving client can safely recognize and ignore the duplicate rather than accidentally processing the same deposit twice on their own side.

14

Design Patterns and Anti-Patterns

14.1 Patterns used

PatternWhere used
Double-Entry BookkeepingLedger Service — the foundational correctness pattern for this entire system
Optimistic Concurrency ControlBalance Reservation Service — fast, contention-free fund locking
Write-Ahead LogDurability and crash recovery for the matching engine
CQRSLedger writes go through the authoritative service; balance reads served from a fast cache
Sharding / PartitioningMatching engines by trading pair; ledger database by account
Circuit BreakerAutomatic withdrawal halt when reconciliation detects a mismatch
Saga PatternCoordinating multi-step deposit confirmation and withdrawal approval workflows

14.2 Idempotency for deposits and withdrawals

Blockchain transactions can be seen multiple times by a node (during reorgs, retries, or duplicate event delivery), so both deposit crediting and withdrawal processing must be idempotent — keyed by the on-chain transaction hash for deposits, and by a unique client-generated withdrawal request ID — ensuring the same blockchain event or client request can never be processed twice.

💡
Software example

If the Blockchain Node Gateway briefly loses connection and, upon reconnecting, re-delivers a deposit event it had already reported once, the Ledger Service recognizes the duplicate transaction hash and safely ignores the second delivery instead of crediting the user’s balance twice.

14.3 Anti-patterns to avoid

Anti-pattern

Single mutable balance column with no audit trail

Impossible to reconstruct history, prone to silent drift, and gives no automatic correctness check.

Anti-pattern

Crediting deposits before sufficient confirmations

Exposes the exchange to reorg-based double-spend attacks.

Anti-pattern

Storing wallet private keys outside the Wallet Service

Expands the attack surface unnecessarily.

Anti-pattern

Nightly-only reconciliation

Leaves a long window where a real discrepancy goes undetected.

Anti-pattern

Floating-point numbers for monetary amounts

Introduces rounding errors that undermine the entire double-entry correctness guarantee.

15

Best Practices and Common Mistakes

15.1 Best practices

Practice

Single path through the ledger

Make every balance-changing operation go through the double-entry Ledger Service — never allow a shortcut path that updates a balance directly.

Practice

Continuous reconciliation + circuit breaker

Run reconciliation continuously, in near real time, with an automated withdrawal circuit breaker wired directly to any detected mismatch.

Practice

Integer fixed-point amounts

Use integer, fixed-point amounts in the smallest unit of each asset everywhere — never floating-point.

Practice

Shadow-test ledger & wallet changes

Shadow-test any change to the Ledger Service or Wallet Service against mirrored production traffic before deployment.

Practice

Right-size the hot wallet

Size the hot wallet based on actual observed withdrawal volume, sweeping excess to cold storage automatically and regularly.

Practice

Multiple independent approvals

Require multiple independent approvals for large cold wallet withdrawals and for any deployment touching balance-critical code.

15.2 Common mistakes

🚨
Common mistakes to avoid
  • Treating the ledger as “just another database table” instead of the safety-critical system of record it actually is.
  • Crediting user balances optimistically before a trade or deposit has actually, durably settled.
  • Underestimating burst trading volume during volatile price moves — sizing infrastructure for average load instead of the 5–10x spikes common in crypto markets specifically.
  • Allowing withdrawal approval and reconciliation checks to be bypassed “just this once” during an incident — this is exactly the kind of exception that historically preceded major exchange failures.
  • Letting the KYC/AML service become a synchronous blocker in the fast trading path — identity and risk status should be checked once and cached, not re-verified against a slow external service on every single order.
  • Failing to rate-limit withdrawal requests per account — without this, a compromised account or a bug in a client integration could attempt to drain funds through rapid repeated withdrawal calls.
16

Real-World / Industry Examples

Reference

Binance, Coinbase and Kraken

Large global exchanges that all publicly emphasize proof-of-reserves and reconciliation practices, reflecting industry-wide lessons learned from earlier exchange failures around custody and balance tracking.

Reference

Proof-of-reserves initiatives

Many exchanges now periodically publish cryptographic proof that their total customer balances (as recorded in the ledger) are fully backed by actual on-chain holdings, essentially exposing the internal reconciliation process described in this tutorial to public, independent verification.

Reference

Traditional stock exchange matching engines

The matching engine and API gateway layers of this design borrow heavily from decades of electronic trading system engineering at exchanges like Nasdaq and NSE, since the core “match buyers and sellers fairly and fast” problem is identical.

Reference

Decentralized exchanges (DEXs)

While outside the scope of this centralized-exchange design, it’s worth noting that DEXs solve the custody problem differently by never taking control of user funds at all, settling trades directly on-chain instead of through an internal ledger. The trade-off is that DEXs typically cannot match the sub-second latency and deep liquidity a centralized exchange’s off-chain matching engine and ledger can provide, which is exactly why centralized exchanges with the architecture described in this tutorial remain dominant for high-frequency, high-volume trading.

🚨
Production example — FTX (2022)

Following the FTX exchange collapse in November 2022, where customer funds were found to have been commingled and misused rather than properly custodied and reconciled, the industry saw a significant push toward mandatory proof-of-reserves and stricter segregation between an exchange’s own operating funds and customer-custodied assets — reinforcing exactly the kind of strict ledger-to-wallet reconciliation and fund segregation this tutorial’s architecture is built around.

16.1 Lessons learned across the industry

Studying these incidents reveals a consistent pattern: the exchanges that failed catastrophically almost always had either no reliable reconciliation process, commingled customer and operating funds, or allowed balance-critical code changes to bypass rigorous review. This is precisely why the architecture in this tutorial treats the Ledger Service, Wallet Service, and Reconciliation Service as being just as safety-critical as the matching engine itself — arguably more so, since a matching engine bug loses trust, but a ledger or custody bug can lose actual customer funds.

A second, related pattern worth internalizing: nearly every major exchange failure was, in hindsight, both a technical and an organizational failure. The technical safeguards described throughout this tutorial — double-entry bookkeeping, continuous reconciliation, hot/cold wallet separation, multi-signature approval — only work if the organization actually enforces them without exception, even under pressure to move quickly or cut costs. A reconciliation alert that gets silenced “just this once” during a busy period, or a withdrawal approval step that gets bypassed to resolve an urgent customer complaint, quietly removes the exact safety net the architecture was built to provide. Building the system correctly is necessary but not sufficient — operating it with discipline matters just as much.

17

FAQ, Summary and Key Takeaways

17.1 Frequently Asked Questions

Q

Why not just use a traditional bank-style balance system instead of double-entry bookkeeping?

Traditional banks actually do use double-entry bookkeeping internally — it has been the standard for correct financial record-keeping for centuries, long before computers. A crypto exchange adopting the same discipline isn’t an unusual choice; it’s applying well-proven accounting principles to a new asset class with unusually strict correctness requirements.

Q

How do you handle blockchain network fees when crediting deposits or processing withdrawals?

Network fees are themselves recorded as separate ledger entries — for withdrawals, the fee is typically debited from the user’s balance (or absorbed by the exchange as a cost, depending on business policy) as its own balanced entry, keeping the fee’s accounting just as auditable as the underlying transfer.

Q

What happens if the Blockchain Node Gateway falls behind or goes offline?

Deposit detection pauses until the gateway catches up or fails over to a redundant node — this is why running geographically distributed, redundant blockchain node infrastructure matters. Withdrawals already queued can still be processed once broadcasting capability is restored, but new deposits simply won’t be detected (and therefore won’t be credited) until the gateway is healthy again, which is a safe failure mode since no incorrect balance updates occur during the outage.

Q

Is this architecture over-engineered for a small, low-volume exchange?

The double-entry ledger and hot/cold wallet split are worth adopting even at small scale, since retrofitting them after a balance-tracking incident is far more painful than building them in from day one. Components like trading-pair sharding or dedicated blockchain node clusters can reasonably be deferred until actual volume demands them.

Q

How would you support listing a brand new cryptocurrency without downtime?

Because the matching engine is sharded by trading pair, adding a new pair is simply a matter of spinning up a new shard with an empty order book, registering it with the Symbol Router, and updating the Wallet Service and Blockchain Node Gateway to support the new asset’s deposit and withdrawal handling — none of this requires touching or restarting any existing trading pair’s shard, so live trading on all other pairs continues completely unaffected.

Q

What is the single most important design decision in this entire system?

If only one idea could be kept from this whole tutorial, it would be this: never let a balance change happen anywhere except through a single, auditable, double-entry Ledger Service that enforces its own correctness invariant on every transaction. Every other mechanism in this design — reservation locking, asynchronous settlement, hot/cold wallet separation, reconciliation — exists to make that one core guarantee both fast and safe at scale, but the guarantee itself is what actually keeps user funds accurate and trustworthy.

17.2 Key Takeaways

What we covered

  • Accurate, auditable balances are achieved through double-entry bookkeeping, where every balance change is a set of debits and credits that must always sum to zero — turning correctness into something that can be automatically verified, not just hoped for.
  • Thousands of trades per second is handled through the same symbol/pair-sharded, single-threaded-per-shard matching engine approach used in traditional trading systems, decoupled from ledger settlement via a durable message queue.
  • Fund safety depends on the hot/cold wallet split, multi-signature approval for large withdrawals, and continuous, automated reconciliation between the ledger and actual on-chain holdings — with a circuit breaker that halts withdrawals the instant anything doesn’t add up.
  • Every component — load balancer, API gateway, OMS, balance reservation, matching engine, ledger, wallet service, blockchain node gateway, KYC/AML, reconciliation, databases, cache, and monitoring — has one clear, isolated responsibility, which is exactly what allows this system to be simultaneously fast, correct, and safely auditable at once.
  • Real-world failures (Mt. Gox, FTX, Knight Capital-style deployment incidents) are not edge cases — they are the direct motivation for nearly every safety mechanism described in this tutorial, and should be treated as required reading for anyone building this kind of system.
Closing principle

A great crypto exchange architecture is judged not by how fast its matching engine runs on a good day, but by how rigorously its ledger, wallet and reconciliation layers protect user funds on the worst day — because in this domain, a single silent balance drift can undo years of engineering trust in a matter of hours.