InterviewKata
System design · Worked design D33 of 46 · Location and marketplaces · Long form

Design a stock exchange

An exchange is a strange system: modest data, enormous message rates, and latency measured in microseconds. But the hardest requirement isn't speed. It's fairness you can prove: every order processed in the order it arrived, every trade reproducible from the record, months later, for a regulator.

1. The prompt

"Design a stock exchange: traders submit buy and sell orders, the exchange matches them and reports trades, and everyone sees the current prices."

What makes it hard:

2. Frame the problem

How many instruments, and how busy?

About 10,000 symbols; peaks of around 500,000 order messages a second across all of them, with the busiest symbol at perhaps 50,000 a second at the open.

Load is spread over symbols, and one symbol's book is small: we can shard by symbol and keep each book in one thread's memory.

Which order types?

Limit orders, market orders, cancel and cancel-replace; time-in-force day or immediate-or-cancel.

The matching rules stay simple: price-time priority. Exotic types are follow-ups.

Who connects, and how?

Brokers and trading firms, many in our data centre (colocation), over a binary or FIX protocol on long-lived TCP connections.

Not REST: every request/response round trip and text parsing costs microseconds we don't have.

What latency do clients expect?

Order in to acknowledgement out in well under a millisecond inside the data centre, consistently.

The tail (p99.9) is the target: no garbage-collection pauses, no locks, no disk waits on the hot path.

What market data do we publish?

Best bid and offer, depth by price level, and every trade, to about 1,000 subscribers.

One stream to many readers at once: multicast, with sequence numbers so gaps are detectable.

Risk checks?

Yes, before matching: per-account limits, price bands against fat-finger errors, and a kill switch.

They're on the hot path, so they must be fast and in memory.

Clearing and settlement?

Out of scope; we hand trades to a clearing house.

Keeps us on matching. The handoff is a durable trade feed.

Audit?

Every order, cancel and trade kept for years, with accurate timestamps; we must be able to reproduce any day exactly.

That points to an event log as the source of truth, and a deterministic engine that can replay it.

3. What it must do

  1. A trader can place and cancel orders and gets an acknowledgement for each.
  2. The exchange matches orders by price-time priority and reports every fill to both sides.
  3. Everyone receives market data: the order book's top levels and every trade, in order.

Not now: clearing and settlement (a separate system), opening and closing auctions (a follow-up), routing to other exchanges, user accounts and onboarding.

4. How well it must do it

TargetWhy this number
Fair and deterministic: orders for a symbol processed strictly in arrival order; replaying the log reproduces every trade exactly consistency · rulesFairness is the product; replay is how you prove it.
Latency: order to acknowledgement under 100 µs at p99 inside the data centre, with a tight tail speedParticipants compare exchanges on it; a slow tail is a fairness problem too.
No lost orders: an acknowledged order or trade survives any single machine failure data safety"Your order was accepted" must stay true.
Failover in seconds, without duplicate or missing trades surviving failuresMarkets can't wait minutes; a double fill is worse than a short halt.
Market data: every subscriber sees the same events in the same order, and can detect and repair gaps consistencyTraders act on it; missing one update means a wrong book.

5. Numbers that change the design

QuantityWorking→ so we…
Orders, busiest symbol50,000 a second; a match is a few hundred nanoseconds of in-memory workOne core can handle one symbol easily → so each symbol's book lives on one thread, with no locks
Orders, total500,000 a second over 10,000 symbolsToo much for one core in total → so we shard symbols across engine processes
Event log500,000 events × ~100 bytes ≈ 50 MB/s; a trading day ≈ 1–2 TBSequential appends are cheap → so every event is journaled and replicated before it's acknowledged
Book sizeA few thousand resting orders per symbolKilobytes to megabytes → so everything fits in memory; disks are only for the log
Network round trip in one data centre~10–50 µsReplicating to a standby before acking costs one round trip → so the standby sits in the same data centre; a second site gets the log asynchronously

Skipped on purpose: storage for history (cheap), and user counts (irrelevant: the load is orders, not users). All figures are illustrative.

6. The nouns

order    order_id, account, symbol, side, price_ticks (integer), qty, type, tif, seq
book     symbol → bids: price → FIFO of orders · asks: price → FIFO of orders
trade    trade_id, symbol, price_ticks, qty, buy_order_id, sell_order_id, seq
event    seq (per shard, gap-free), time, kind (new | cancel | fill | …), payload   (append-only)

Prices are integers in ticks (the minimum price step), never floating point: two orders at "the same price" must compare equal.

7. The contract

Order entry (binary over TCP, or FIX)
  NewOrder      {client_order_id, symbol, side, price, qty, type, tif}
  Cancel        {client_order_id}
  ← Ack         {client_order_id, exchange_order_id, seq}
  ← Fill        {exchange_order_id, price, qty, trade_id, leaves_qty}
  ← Reject      {client_order_id, reason}

Market data (UDP multicast, one stream per channel of symbols)
  ← AddOrder / Cancel / Trade / … each with a channel sequence number
  Retransmission: TCP request for missed sequence numbers
  Snapshot: the full book every few seconds, for late joiners

The account comes from the authenticated session, never from the message. client_order_id is unique per session, so a resent order is recognised, not doubled.

Data flow: gateway (session, risk checks) → sequencer (assigns the order) → replicated log → matching engine (deterministic) → acknowledgements and fills to gateways, events to market data → trade feed to clearing.

8. First working version

Gateways, one matching engine holding every book in memory, an execution report back to the trader, and a market data publisher.

8.1 A limit order that rests

A buy at 100.02 when the best ask is 100.05 can't trade, so it joins the bid side at its price level, behind orders already there.

8.2 An order that matches

A sell for 300 at 100.00 is "aggressive": it crosses the best bid. It fills against the best price first, and within a price, the oldest order first. Each fill produces execution reports for both sides and one trade on the market data feed.

8.3 Cancel

The engine finds the order through a map from order id to its place in the book and unlinks it in O(1). If it already filled, the cancel is rejected: whatever reached the engine first wins.

Parked for laterthe book's data structure (9.1) · ordering and determinism (9.2) · losing the engine mid-trade (9.3) · market data to a thousand subscribers (9.4) · microsecond latency (9.5) · risk checks (9.6).

9. Make it hold up

9.1 The order book latency

ApproachWhy
✗ BadA database table of orders, queried for the best price on every orderMilliseconds per match, and locks between concurrent orders.
~ GoodIn memory: two sorted maps (price → FIFO list) and a map from order id to list nodeO(log P) to find a level, O(1) to add, fill and cancel at a level. Fine for most symbols.
✓ GreatPrice levels in an array indexed by tick around the current price, plus the sorted map for far-away prices; intrusive linked lists and preallocated order objectsThe touch (best prices) is O(1) with no allocation, so no garbage-collection pauses and very few cache misses.

Matching an incoming order walks the opposite side from the best price, filling the oldest orders at each level until the order is filled or the price no longer crosses. Then any remainder rests (a limit order) or is cancelled (market or immediate-or-cancel).

9.2 One sequence, one thread fair and deterministic

ApproachWhy
✗ BadMany threads matching one book with locksLock contention adds latency, and the order of fills depends on thread scheduling: not reproducible, arguably not fair.
~ GoodA queue per symbol, one thread per bookDeterministic per symbol, but the order between gateways depends on queue timing that isn't recorded.
✓ GreatA sequencer stamps every inbound message with a gap-free sequence number and appends it to the log; the matching engine is a deterministic state machine that applies the log in order on a single threadArrival order is decided once and recorded. Same log, same trades, every time: on the standby, in tests, in an audit. This is the design LMAX described publicly.

Determinism has rules: no reading the clock inside the engine (time comes in the event), no random numbers, no iteration over unordered hash maps, no floating point for prices.

9.3 Surviving the loss of an engine no lost orders · failover

ApproachWhy
✗ BadSnapshot the book to disk every minuteA crash loses up to a minute of acknowledged orders.
~ GoodWrite each event to local disk before acknowledgingSurvives a process crash, not the machine.
✓ GreatReplicate each sequenced event to hot standbys before acknowledging; standbys apply the same log and hold the same book; on failure one is promoted with a fencing token (Module 21) so the old primary can't keep tradingFailover in seconds with the exact same state; no double fills, because only one fenced leader writes to the log.

Periodic snapshots still help: a restarting replica loads the latest snapshot and replays only the log after it. A second data centre receives the log asynchronously for disaster recovery, accepting a small loss window there.

9.4 Market data to a thousand subscribers market data

9.5 Microseconds latency

9.6 Risk checks before matching rules

The gateway checks each order against the account's limits (open exposure, order size) and a price band around the last trade, using state kept in memory and updated from the engine's fills. A kill switch blocks all new orders from an account instantly. These checks run before the sequencer, so a rejected order never enters the book; the engine stays simple and fast.

9.7 The whole design after the deep dives

10. Interviewer follow-ups

The market opens and one symbol gets 20× its normal traffic.

One thread per symbol still copes: matching is sub-microsecond. If a symbol truly outgrows a core, it gets a dedicated engine process. Many exchanges also open with an auction: orders collect for a period, then one price is computed that matches the most volume, which spreads the opening burst.

How do you add more symbols or more traffic overall?

More engine shards, each owning a set of symbols with its own sequencer and log. Gateways route by symbol. There are no cross-symbol transactions, which is what makes this sharding clean.

The price moves 10% in a second.

Volatility controls: price bands that reject orders far from the reference price, and trading pauses (circuit breakers) that halt a symbol briefly, as US equity markets do under their limit up-limit down rules.

Two gateways receive orders at the same nanosecond. Which is first?

Whichever the sequencer stamps first; that's the definition. To make it fair, keep paths from gateways to the sequencer equal in length and latency, and record arrival timestamps from synchronised clocks for audit.

A regulator asks why order X didn't fill at 10:31:02.

Replay the log up to that sequence number in a test engine and inspect the book at that moment. Determinism makes this an exact answer, not a reconstruction.

The primary engine is slow, not dead.

Health checks on latency, not just liveness; a stuck primary is fenced and a standby promoted. Fencing matters most here: a slow primary that wakes up must not write.

A subscriber loses packets constantly.

That's its problem to fix (network, buffers), but it can always recover: retransmission for small gaps, snapshots for large ones. The feed never slows down for it.

How would a crypto exchange differ?

Usually internet clients over WebSockets instead of colocation, 24/7 trading so no daily restart, custody of assets in wallets, and often less strict latency. The matching engine core is the same.

Can we run the engine in the cloud?

The logic, yes. The latency and fairness targets are hard with shared, virtualised networks; that's why the biggest exchanges run in their own data centres, though some cloud deployments exist.

11. When things break

FailureWhat users seeHow we detect itWhat limits the damage
Engine primary diesA pause of seconds for its symbolsHeartbeats from the primaryHot standby with the same book; fencing
Sequencer or log replica lostNothing, or a brief pauseReplication acks missingAcknowledge only after replication; replace the replica
Gateway downIts sessions disconnectSession heartbeatsClients reconnect to another gateway; client order ids prevent duplicates
Market data packet lossA subscriber's book briefly wrongSequence gapsRetransmission, snapshots, A/B feeds
Runaway algorithmFloods of bad orders from one firmRisk limits, order-rate limitsPrice bands, kill switch per account
Whole data centre lostTrading haltsSite monitoringAsynchronous log copy in a second site; resume after reconciliation

12. Wrap-up and the clock

Trade-offs we made:

What we'd watch: order-to-ack latency (p50, p99.9, max) per shard · sequencer and replication lag · rejected orders by reason · market data gaps and retransmission requests · failover drills passed.

At 10×: more symbol shards and gateways; the per-symbol design doesn't change, because no single book gets 10× bigger work than a core can do.

A 60-minute interview

0–5Frame: symbols, order rates, latency, audit
5–10Must do, how well, numbers
10–15Nouns, protocol, data flow
15–25First version: rest, match, cancel
25–50Deep dives: book (9.1), sequencer (9.2), failover (9.3)
50–60Market data, follow-ups, wrap-up

A 90-minute interview

0–8Frame, including risk and audit
8–15Must do, how well, numbers
15–22Nouns, protocol, data flow
22–35First working version
35–75Deep dives 9.1–9.6 and the final picture
75–85Follow-ups: the open, halts, replay for a regulator
85–90Trade-offs, metrics, 10×
LevelWhat a strong answer shows
Mid-levelAn in-memory order book with price-time priority, matching, cancels, acknowledgements and a trade feed.
SeniorThe sequencer and deterministic single-threaded engine, replication before acknowledgement, sharding by symbol, multicast market data with gap recovery.
Staff+Determinism as the basis of fairness, failover and audit; fencing; tail latency engineering; volatility controls; the business trade-offs of colocation and second-site loss windows.

Common mistakes:

13. Try it, check yourself, read more

Order book simulator. Send orders into a small book for one symbol and watch price-time priority work: best price first, then oldest first. Sequence numbers show arrival order.

Asks (sell)QtyOrders, oldest first
Bids (buy)QtyOrders, oldest first

Prices are stored as integer ticks of 0.01. Try: buy 250 at 100.06 (fills 120 at 100.05, then 130 at 100.06 from order 7, the older one there), then a market sell of 600 (it walks down the bids: 100.03, then 100.02 oldest first, then 100.00).

What does price-time priority mean?

Fair and predictable: improve your price, or arrive earlier.

Why must the matching engine be deterministic?

Determinism gives failover, testing and audit from the same log.

When should an order be acknowledged?

An acknowledged order must survive the loss of one machine.

How should market data reach a thousand subscribers?

Everyone gets each packet at once; gaps are detected and repaired.
ResourceWhy
The LMAX Architecture (Martin Fowler)A single-threaded, in-memory, event-sourced trading engine, explained.
LMAX DisruptorThe ring buffer that passes events between threads without locks.
How to Build an Exchange (talk, YouTube)Sequencers, determinism and market data from people who run trading systems.
FIX standardsThe industry protocol for order entry.
AeronOpen-source low-latency messaging with replicated logs, used in trading systems.