System design interview
Design a stock exchange matching engine
Order books, price-time priority, a sequencer, hot standby and market data fan-out.
Press Play to follow a request through the design, or step through it.
- Client and edge
- Service
- Data store
- A broker sends a limit order: buy 220 shares of one symbol at 10.03.
- The gateway authenticates the broker and passes a parsed order to the risk check.
- The risk check approves it. The price is inside the band, and the order adds 220 × 10.03 = 2,206.60 to exposure, under the limit.
- The sequencer stamps it with the next sequence number and sends it to the primary matching engine.
- At the same time it writes the order to the replicated log. Results are held back until that write is safe.
- The standby engine reads the log and applies the same order to its own copy of the book.
- Brokers
- Order gateway: auth, parse
- Risk check: limits, exposure
- Sequencer: one global order
- Market data: builds the feed
- Event log: replicated
- Matching engine: one thread/symbol
- Subscribers
- Standby engine: same book
On this page
In 2 minutes
The compute is small. Even the busiest symbol at 25,000 messages a second uses 12.5% of one core, so the design is about order, fairness and failover. Brokers connect to a gateway, then a risk check applies price bands, exposure limits and self-trade rules before an order can reach the book. A sequencer gives every message the next sequence number, sends it to the matching engine and writes it to a log replicated to three nodes.
The engine runs one thread per symbol and is a pure function of the sequenced input, using whole-number prices, so replaying the log gives identical trades. The book keeps a FIFO queue per price level and a map from order ID for constant-time cancels. A hot standby replays the same log and builds the same book. If the primary stops, an epoch number fences it off, and the standby takes over within a second with no gap and no duplicate, since outputs are deduplicated by sequence number.
Results return through the sequencer to the sender, and a publisher broadcasts market data once, with sequence numbers and resend on gaps, because sending to each subscriber would need 200 Gbit/s. The cost is strict determinism everywhere.
Step 1 of 6: Clarify the scope
Interviewer asks
Design the core of a stock exchange. Brokers send buy and sell orders. The system matches them by price and then by arrival time, tells each sender what happened, and publishes trades and prices to everyone. It must be fast, fair, and never lose an accepted order.
Questions to ask first
Ask these before you draw anything. Under each one is why its answer changes the design.
- Which order types do we support: limit, market, cancel?
Each type changes the matching rules. Limit, market and cancel are enough to show the core design.
- How many symbols and how many messages a second at the peak?
Symbols are independent of each other, so they are how we split the load. The busiest symbol sets the limit of one thread.
- What latency do we target, and does fairness matter more than raw speed?
In a market, two senders must be treated by one rule: first in, first served. That drives a single ordering point.
- What happens when a machine fails in the middle of trading?
This sets how the state is replicated, and how long the market can be down: seconds, not minutes.
- Do all participants get market data at the same moment?
Fair, ordered delivery decides whether we stream to each subscriber one by one or broadcast one copy to all.
Requirements
Functional
- Accept limit, market and cancel orders for each symbol.
- Match by price-time priority: the best price first, and the earliest order first at the same price.
- Send the sender an execution report for every change to their order.
- Publish every trade and every change to the book to all subscribers.
- Reject bad orders before they reach the book, using risk checks.
Non-functional
- From order in to acknowledgement out: p99 under 1 ms.
- The same input sequence always produces exactly the same trades.
- No accepted order is lost: it survives the loss of any one machine.
- A standby takes over within 1 second, with no gap and no duplicate trade.
Step 2 of 6: Size it
The compute is small. One thread has 8 times the headroom it needs for the busiest symbol. The hard parts are order, fairness and failover: one place that decides who was first, a deterministic engine, and a log that lets a standby become identical to the primary.
Assumed for the exercise, not facts about a real system:
- There are 5,000 symbols. The peak is 500,000 inbound messages a second, counting new orders and cancels. The busiest symbol gets 5% of them.
- The session runs 6.5 hours (23,400 s) and averages 100,000 messages a second.
- Matching one message takes about 5 microseconds on one core. A message is 64 bytes.
- A book holds about 4,000 resting orders. A market data update is 50 bytes, and there are 1,000 subscribers.
- Load on the busiest symbol
- 500,000 × 5%
- 25,000 messages a second
- CPU for that symbol
- 25,000 × 5 µs = 125 ms of work per second
- 12.5% of one core, which can do 200,000 a second
- Latency budget
- Gateway 50 + risk 20 + sequencer 10 + log copy 100 + match 5 + out 50 µs
- 235 µs, inside the 1 ms goal
- Input log per day
- 100,000/s × 23,400 s = 2.34B messages × 64 B
- ≈ 150 GB a day
- Memory for all books
- 4,000 orders × 64 B = 256 KB per symbol; × 5,000
- ≈ 1.3 GB, and a hot book fits in a core's cache
- Market data, if sent one by one
- 500,000 × 50 B = 25 MB/s = 200 Mbit/s; × 1,000 subscribers
- 200 Gbit/s, so one broadcast copy is needed
Step 3 of 6: Sketch the path of one request
Step through the diagram at the top of the page to watch a request move through these parts.
Brokers connect to the Order gateway, which authenticates them and turns their messages into a standard form. Each order next goes through the Risk check, which rejects anything outside the participant's limits before it can touch the book.
The Sequencer is the single point that decides the order of events. It gives every accepted message the next sequence number, sends it to the primary Matching engine, and writes it to the replicated Event log. The engine is single-threaded for each symbol and is a pure function of the sequenced input: the same input always gives the same trades. A Standby engine reads the same log and builds an identical book.
The engine's results come back through the sequencer, which numbers them too. They go two ways. Execution reports go back along the path of the order to the sender, and update the risk check's view of exposure. The Market data publisher broadcasts trades and book changes to the Subscribers.
Other paths through the system
Report to the sender
- The engine matched the order against the book: 150 shares traded at 10.02 and 70 at 10.03, so it is filled. It returns those events to the sequencer.
- The sequencer numbers the events and sends them back. The risk check moves the filled quantity from open buys into position, at the fill prices.
- The execution report passes on to the gateway that holds the broker's connection.
- The broker receives the fills and the remaining quantity, in sequence order.
Publish market data
- The same sequenced events include two trades and a change: the best offer is now 130 shares at 10.03.
- The sequencer hands them to the market data publisher, with their sequence numbers.
- The publisher broadcasts each update once. Subscribers use the sequence numbers to spot a gap and ask for a resend.
Step 4 of 6: Go deep on the hard part
The order book and price-time priority
Interviewer asks
What data structure holds the book, and how does an incoming order match?
Each symbol has its own book with two sides. Bids (buys) are sorted from the highest price down. Asks (sells) are sorted from the lowest price up.
Read moreShow less: The order book and price-time priority
Prices and quantities are whole numbers, in ticks and shares, never floating-point, so there is no rounding and results are identical on every machine.
Each price level holds a first-in, first-out queue of resting orders. That is the time priority. The book keeps the levels in a sorted structure, or in an array indexed by tick when the price band is bounded. A hash map from order ID to the order's place in its queue makes a cancel a single step, with no search.
Matching a limit order. An incoming buy at price P looks at the best ask. While it has quantity left and the best ask is at or below P, it trades with the oldest order at that level. The trade happens at the resting order's price. Whatever is left rests at the back of its own level.
A worked example. The asks are 100 shares (A) at 10.02, 50 (B) at 10.02, and 200 (C) at 10.03. A buy of 220 at 10.03 arrives. It trades 100 with A and 50 with B, both at 10.02. It then trades 70 with C at 10.03, leaving C with 130. The buyer asked to pay up to 10.03 but got 150 shares at the better price of 10.02.
A market order has no price. It takes whatever the book offers until it is filled or the book is empty, and any rest is cancelled; it never rests. A cancel finds the order by ID and unlinks it. If the order was already filled, the cancel is rejected as too late. Whichever of the two was sequenced first wins, which is what makes the answer fair.
Cutting the quantity of a resting order keeps its place in the queue. Raising it, or changing the price, sends it to the back. Matching costs time in proportion to the number of trades it makes. Cancels, which are most of the traffic, are constant time.
The sequencer, a single thread and the standby
Interviewer asks
How do you make matching deterministic and fast, and survive a failure of the engine?
Matching must be a pure function of an ordered stream: new book = match(old book, next event).
Read moreShow less: The sequencer, a single thread and the standby
If the order of events is fixed, the result is fixed. So one component, the sequencer, decides the order. It stamps every message with the next sequence number. That is the answer to 'who was first', and it removes ties.
One thread per symbol. With a single writer there are no locks and no races. The book fits in the core's cache, about 256 KB, and the loop avoids memory allocation and system calls. At about 5 microseconds a message, one thread handles 200,000 a second, against 25,000 needed for the busiest symbol. To scale, spread symbols over many engine threads or machines. Never share one symbol between threads.
The replicated log. The sequencer appends each event to a log that is copied to three nodes, and it counts as written when two have it. The engine does not release a result until its input is safe. The engine's state is just a cache of the log. A snapshot of the book plus its sequence number is saved every few minutes, so recovery replays only the tail.
The hot standby reads the same log and applies every event in the same order, so its book is identical. It discards its outputs while it is not in charge. If the primary stops answering for, say, 200 ms, the standby takes over. First fence off the old primary: every output carries an epoch number, and a lower epoch is ignored downstream, so a primary that is only slow cannot emit stale results. Then replay any events past what it applied. Because the engine is deterministic, the results it regenerates are byte for byte what the old primary produced, and anything already published is dropped by its sequence number.
Do not read the wall clock in the matching code. Timestamps are assigned by the sequencer and carried in the event, so a replay is exact. The sequencer is a single point too, so run it as a primary and a backup with the same epoch rule, continuing from the last sequence number in the log.
Risk checks and market data
Interviewer asks
What stops a bad order from reaching the book, and how does one engine feed a thousand subscribers fairly?
Risk checks run before the sequencer, so a rejected order takes no sequence number and never touches the book.
Read moreShow less: Risk checks and market data
They are in-memory counters per participant, so each check takes microseconds.
- Fat-finger limits. Reject a price far from a reference, say outside 10% of the last trade, or a quantity above a cap, say 1 million shares.
- Exposure. Keep a worst-case figure per participant. For buys, it is the open buy quantity times each order's limit price, plus the value of the current position at the last trade price. Add the new order's quantity times its price, and reject it if the total passes the participant's limit. Sells are checked the same way against short exposure. A fill turns open quantity into position at the fill price, and a cancel or a sell lowers the figure.
- Rate caps. Limit messages a second per connection, so one runaway program cannot flood the engine.
- Self-trade prevention. Do not match a participant against itself. Cancel the older or newer order by a stated rule.
- A kill switch. One command cancels all of a participant's orders and blocks new ones.
Market data. The publisher turns the sequenced events into two streams. The incremental stream carries every change to the book and every trade, each with a sequence number. A snapshot stream repeats the top levels of each book every second or so, so a late joiner can catch up.
Sending 25 MB a second to each of 1,000 subscribers one by one is 200 Gbit/s, which no single publisher can do. Instead send each update once to a network that copies it, such as multicast inside the data centre, with a distribution tier beyond it. Subscribers watch the sequence numbers. On a gap, they ask a resend server for the missing range or wait for the next snapshot.
Fairness. Everyone should see events in the same order and at about the same time. Because outbound events also pass through the sequencer, there is one global order for reports and public data alike. Give co-located participants cables of equal length, so geography does not decide who is first. The event log doubles as the audit trail, kept for as long as regulators require.
Step 5 of 6: Name the trade-offs
Threading
One thread per symbol, no locks over A shared book with many threads and locks
It is simple, fast and deterministic. The cost is that one symbol is limited by one core, so we shard symbols and watch the busiest one.
What to replicate
The ordered input log, with snapshots over Copying the engine's state after every change
Input events are small, and any machine can rebuild the book by replaying them. The cost is that the engine must be strictly deterministic.
Numbers
Whole-number ticks and shares over Floating-point prices
No rounding differences between a primary and a standby. The cost is converting at the edges, and care with the price scale.
Distributing market data
Broadcast once, with sequence numbers and resend on a gap over A separate connection to each subscriber
The cost of the feed does not grow with the number of subscribers, and everyone gets the same stream. The cost is gap handling in every client.
Step 6 of 6: Say what breaks next
Questions an interviewer asks once the design is on the board. Answer each one out loud first.
- How do you halt trading in one symbol?
A halt is just another event in the sequenced stream. After it, the engine rejects new orders for that symbol, or accepts only cancels, and the standby does the same at the same point. A resume event ends the halt, often through an auction that reopens the market at one price.
- How would an opening auction work?
During the call period the engine collects orders and does not match them. At the cut-off it finds the one price that would trade the most shares and matches everything at that price, oldest first. Because it is a pure function of the collected orders, a replay gives the same price.
- How do you add more symbols or more load?
Shard by symbol. Each shard has its own sequencer, log, engine and standby, since no order spans two symbols. Ordering is only needed inside a symbol, so the shards never have to agree with each other.
- How do you prove the primary and standby really match?
Compute a hash of each book every few thousand events and compare the two. A mismatch means a bug or a hardware fault, so alert at once. Also replay a full day of recorded input on a fresh engine in a test, and require byte-identical output.