System design interview

Design a stock exchange matching engine

Order books, price-time priority, a sequencer, hot standby and market data fan-out.

At a glance

Level
Advanced
Topics
Consistency, Real-time
Main parts
Order gateway · Risk check · Sequencer · Market data · Event log · Matching engine · Standby engine
What you'll learn
Practise this problem
Overview
BrokersOrdergatewayRisk checkSequencerMarket dataEvent logreplicatedMatchingengineSubscribersStandbyengineBrokersOrder gatewayauth, parseRisk checklimits, exposureSequencerone global orderMarket databuilds the feedEvent logreplicatedMatching engineSubscribersStandby enginesame book

Press Play to follow a request through the design, or step through it.

  • Client and edge
  • Service
  • Data store
  1. A broker sends a limit order: buy 220 shares of one symbol at 10.03.
  2. The gateway authenticates the broker and passes a parsed order to the risk check.
  3. 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.
  4. The sequencer stamps it with the next sequence number and sends it to the primary matching engine.
  5. At the same time it writes the order to the replicated log. Results are held back until that write is safe.
  6. 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

Key ideas
  • The numbers. The compute is small. One thread has 8 times the headroom it needs for the busiest symbol.
  • Threading. One thread per symbol, no locks.
  • What to replicate. The ordered input log, with snapshots.
  • Numbers. Whole-number ticks and shares.
  • Distributing market data. Broadcast once, with sequence numbers and resend on a gap.
Read the 2-minute answer

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

Questions to ask first

Ask these before you draw anything. Guess why each answer changes the design, then open it to check.

Which order types do we support: limit, market, cancel?
Why it matters: 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?
Why it matters: 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?
Why it matters: 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?
Why it matters: 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?
Why it matters: 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

Show the maths

These numbers are assumptions 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.

The arithmetic:

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

  1. 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.
  2. 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.
  3. The execution report passes on to the gateway that holds the broker's connection.
  4. The broker receives the fills and the remaining quantity, in sequence order.

Publish market data

  1. The same sequenced events include two trades and a change: the best offer is now 130 shares at 10.03.
  2. The sequencer hands them to the market data publisher, with their sequence numbers.
  3. 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

InterviewerWhat 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.

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. 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

InterviewerHow 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).

Matching must be a pure function of an ordered stream: new book = match(old book, next event). 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

InterviewerWhat 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.

Risk checks run before the sequencer, so a rejected order takes no sequence number and never touches the book. 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
Chose
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
Chose
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
Chose
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
Chose
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

Follow-up questions an interviewer asks once the design is on the board. Answer each one out loud, then open it to compare.

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.

Free ATS check

See your resume the way an ATS reads it, then fix what it misses.

Drop in a PDF for a score out of 100, every fix that cost a point, and the line it belongs to. It runs in your browser, with no signup for the score.