Financial & Trading
Design High-Throughput Stock Exchange
01
The problem
The problem
An exchange is a meeting point. Brokers and trading firms send it orders to buy or sell a listed instrument at some price, and the exchange decides, deterministically and in public, which orders trade with which. Everything else, from the order entry links to the market data feeds, exists to make that one decision fast, fair and provable.
Speed matters more here than in almost any other system you will design in an interview. Firms co-locate their servers in the exchange's data centre and measure the round trip from sending an order to seeing its acknowledgement in tens of microseconds. A garbage collection pause of a few milliseconds would be an incident. So would two replicas disagreeing on whether a trade happened.
The design that comes out of these constraints looks unusual: a single thread per group of instruments doing all the matching, an input log that defines the truth, standby machines that replay the same log in lockstep, and networking that bypasses the operating system kernel. This page walks through why each of those choices is the sensible one.
Clearing, settlement and the broker apps that retail customers use are separate systems. Here you are designing order entry, matching, market data and the reliability around them. Say that at the start so the interviewer knows you will not spend twenty minutes on user accounts.
02
What the exchange must do
What the exchange must do
- Accept new orders, cancels and amendments from member firms over a binary order entry protocol and over FIX.
- Support limit, market, immediate-or-cancel, fill-or-kill and post-only orders, plus stop orders that the engine triggers itself.
- Match orders by price first, then arrival time at each price, and report every fill to both sides.
- Publish every change to every order book as a public market data feed, in real time.
- Run opening and closing auctions, and halt trading in an instrument when price moves breach configured bands.
- Enforce pre-trade risk limits per firm: credit, maximum order size, and price collars.
- Order in to acknowledgement out in under 20 microseconds at the median and 60 microseconds at p99, measured at the edge of our network.
- Deterministic: replaying the same input sequence always produces exactly the same trades and feed messages.
- No acknowledged order or trade is ever lost, and failover to a standby completes in milliseconds without reordering anything.
- Fair: two orders that reach the exchange in the same order are always processed in that order, regardless of which gateway they used.
- Sustain bursts over a million messages a second at the open and close without latency spikes.
03
Sizing the load
Sizing the load
Assume a mid-sized equities venue: about 8,000 listed instruments, 400 member firms, and 2 billion order messages a day across a 6.5-hour session. Most messages are not trades. Market makers constantly cancel and replace quotes, so the ratio of messages to trades is about 50 to 1.
| Quantity | Working | Result |
|---|---|---|
| Order messages per day | assumption | 2B |
| Trading seconds per day | 6.5 h x 3,600 | 23,400 s |
| Average message rate | 2B / 23,400 s | about 85,000 / s |
| Burst rate at the open | 20x average, sustained for seconds | about 1.7M / s |
| Trades per day | 2B / 50 | about 40M |
| Resting orders at any moment | assumption across all books | about 30M |
| Order book memory | 30M orders x 64 B, plus price level arrays | about 2 GB |
| Journal written per day | 2B input events x 128 B | about 256 GB |
| Journal rate at peak | 1.7M / s x 128 B | about 220 MB / s |
| Market data messages at peak | about 1.2 feed messages per input | about 2M / s, 80 MB / s per feed line |
| Matching partitions | instruments split across single-threaded engines | 16 engines, hottest near 300K msgs / s |
The data is small. Two gigabytes of order books and a quarter of a terabyte of journal per day fit on a single machine. The difficulty is entirely in latency and determinism: handling a million and a half messages a second with microsecond response times and no variation.
A well-written matching loop handles several million operations a second on one core because everything it touches fits in CPU cache. Sixteen engines give five to ten times headroom over the busiest second of the year, even with the most active instruments concentrated in one partition.
04
API design
API design
Order entry over a binary protocol
Low-latency firms use a fixed-layout binary protocol over TCP, in the style of NASDAQ OUCH. Every field sits at a known offset, so the gateway parses without branching or allocating.
Prices travel as integer ticks, never as floating point. A stock with a one-cent tick trading at 182.45 is sent as 18,245. That keeps comparisons exact and lets the engine use the price as an array index.
Order entry over FIX
Slower clients and most brokers use FIX 4.4 or 5.0, a tag and value text protocol. A dedicated FIX gateway translates each message into the same internal binary form before it reaches the sequencer, so the engine never parses text.
Execution reports back to the firm
Public market data
The public feed is sent as UDP multicast, one message per book change, wrapped in packets with a session ID and a sequence number. Subscribers that miss a packet ask a retransmission server for the range, or rebuild from a periodic snapshot channel.
05
Architecture overview
Architecture overview
flowchart LR
F["Member firms"] --> GW["Order entry gateways"]
GW --> RK["Pre-trade risk"]
RK --> SQ["Sequencer"]
SQ --> J[("Journal on two machines")]
SQ --> ME["Matching engine, primary"]
SQ --> MB["Matching engine, standby"]
ME --> OUT["Output publisher"]
OUT --> GW
OUT --> MD["Market data, feeds A and B"]
OUT --> DC["Drop copy and clearing"]
MD --> SUB["Subscribers"]
MB -. "compare outputs" .-> OUTEvery order follows the same path. A gateway terminates the firm's session, validates the message and converts it to the internal format. Pre-trade risk checks the firm's credit and the order's size and price against limits held in memory. The sequencer stamps it with the next number in the input log for its partition, writes it to a journal replicated on two machines, and only then passes it to the matching engine.
The matching engine is a single thread that reads sequenced events one at a time and updates the order books it owns. It is the only component that changes book state. Its outputs, acknowledgements, fills and book changes, go to an output publisher that sends private reports back through the gateways, public updates to the market data feeds, and a complete copy to clearing and to firms' drop-copy sessions.
A standby engine reads exactly the same sequenced input and produces exactly the same output, because the engine is deterministic. Nobody listens to the standby while the primary is healthy, but its output is compared with the primary's, which catches bugs and hardware faults. If the primary dies, the standby is already in the same state and takes over.
Physical layout
Firms rent racks in the exchange data centre. Every cross-connect from a firm's rack to our switches is cut to the same cable length, so no firm is closer than another.
Each engine host pins the matching thread to an isolated core, disables frequency scaling and hyper-threading on that core, and keeps the books in memory local to that CPU socket.
Low-latency switches with cut-through forwarding, kernel-bypass network cards on gateways and engines, and precision time protocol for hardware timestamps accurate to tens of nanoseconds.
A disaster recovery site in another city receives the journal continuously. Failover there takes minutes and is a business decision, unlike the in-site standby, which takes over automatically.
06
Deep dives: order books, determinism, latency and fairness
Deep dives: order books, determinism, latency and fairness
Interviewers pick two or three of these topics and push hard. You do not need all of them, but you should know how the order book is laid out in memory, why the engine is single-threaded and deterministic, how failover avoids losing anything, and how market data reaches thousands of subscribers at once.
1. Laying out the order book
A book has two sides. Each side is a set of price levels, and each level is a first-in, first-out queue of resting orders. Three operations dominate: add an order at a level, cancel an order by its ID, and match against the best level on the other side. Cancels are by far the most common, often 80% or more of all messages, so they must be cheap.
The classic layout uses three structures. Price levels live in an array indexed by tick offset from a reference price, so finding the level for a price is arithmetic, not a search. Within each level, orders form an intrusive doubly linked list, where the next and previous pointers live inside the order record itself, so removing one order is constant time with no allocation. And a hash map from order ID to order record lets a cancel jump straight to its node.
Order records come from a pre-allocated pool and are sized to fit in one 64-byte cache line. Nothing is allocated during trading. When a level empties, the engine moves its best-price pointer to the next populated level, and a small bitmap over the price array makes that scan a few instructions.
Why not a balanced tree of price levels? A tree works and is a fine first answer, but every lookup chases pointers across memory, and each cache miss costs around 100 nanoseconds. For instruments whose price stays within a few thousand ticks of the reference during a day, the array wins comfortably. Instruments with very wide ranges can fall back to a tree or a sparse two-level array.
2. Matching
When an aggressive buy order arrives, the engine looks at the best ask. If the order's limit is at or above it, the two trade at the resting order's price, the quantities reduce, and fully filled orders leave the book. The engine repeats against the next resting order, then the next level, until the incoming order is filled or its limit no longer crosses. Whatever remains rests in the book, unless the order is immediate-or-cancel, in which case it is cancelled.
Other order types are rules wrapped around this loop. Fill-or-kill checks available quantity up to the limit price before touching anything and rejects if there is not enough. Post-only rejects or reprices an order that would cross. Stop orders sit outside the book in a trigger table keyed by price, and the engine releases them as normal orders when a trade prints through their trigger.
3. Determinism is the foundation
The engine is a pure function of its input log. Given the same sequence of events, it must produce the same trades, the same IDs and the same feed messages, byte for byte. That rule bans several things you would use without thinking in normal services: reading the wall clock inside the engine, iterating a hash map in an order that depends on memory addresses, using threads that might interleave differently, or making any network call. Time comes from the sequencer, stamped onto each event before it enters the log.
What you get in return is enormous. Replication becomes "run the same log on another machine". Recovery becomes "load a snapshot and replay from there". Debugging a production incident becomes "replay yesterday's log on a laptop and step through the exact trade". Regulators can ask how a trade happened and receive a precise answer.
4. Single thread, no locks
A multi-threaded engine needs locks or atomic operations on the books, and both introduce contention and timing variation. A single thread per partition does each operation in a few hundred nanoseconds with no coordination at all. Partitioning by instrument gives parallelism across the venue, because orders for one instrument never need to see another instrument's book.
Between components, events move through ring buffers in the style of the LMAX Disruptor: a pre-allocated array with one writer and sequence counters for each reader. Writers and readers never take a lock, they only publish and read sequence numbers. Each counter sits on its own cache line, so two cores updating neighbouring counters do not keep invalidating each other's caches. Threads busy-spin on dedicated cores rather than sleeping, because waking a sleeping thread costs microseconds.
5. Getting the kernel out of the way
A normal network receive involves an interrupt, a copy into kernel memory, the TCP stack, a system call and another copy. That is several microseconds before your code sees a byte. Gateways and engine hosts use kernel-bypass network stacks, such as DPDK or vendor libraries like Solarflare's, which map the network card's queues straight into user space. The application polls the card directly and handles packets without any system call.
Hardware timestamps from the network card record when each packet hit the wire. They are used for latency monitoring and for regulatory clock-sync requirements, never for ordering decisions inside the engine, which uses the sequencer's order only.
6. Journal, replication and failover
flowchart TD
A["Event arrives at sequencer"] --> B["Assign sequence number"]
B --> C["Write to local journal"]
B --> D["Send to replica journal"]
C --> E{"Both persisted?"}
D --> E
E -->|yes| F["Release to primary and standby engines"]
F --> G["Engines emit identical outputs"]
G --> H["Publisher sends primary output"]An order is not acknowledged until its sequenced event is persisted on two machines. Writes go to NVMe through a memory-mapped or direct I/O journal, and the replica receives the same bytes over a dedicated link. This adds a few microseconds, and it is the price of never losing an acknowledged order.
The standby engine consumes the same released events. When monitoring declares the primary dead, a single arbiter promotes the standby and fences the old primary by incrementing an epoch that the publisher checks on every output. Any output stamped with an older epoch is dropped, so a primary that was only slow, not dead, cannot publish conflicting results. The standby resumes from the next sequence number, and because it already processed every released event, no order is reordered or replayed to clients.
Recovery from cold uses snapshots. Every few minutes the engine writes its full state, marked with the last sequence it applied, without pausing: the standby takes the snapshot so the primary never stops. Restoring loads the latest snapshot and replays the journal from that sequence onwards.
7. Market data to thousands of subscribers
Sending each subscriber its own TCP stream would mean thousands of copies of every update, all slightly out of step. Instead the exchange sends one stream as UDP multicast, and switches copy packets to every subscribed port at the same moment. That makes the public view simultaneous for everyone in the hall.
UDP can drop packets, so every packet carries a sequence number. The feed is sent twice, on independent A and B lines through different switches, and subscribers take whichever copy arrives first. If both are lost, the subscriber requests the missing range from a retransmission server, or rejoins from a snapshot channel that cycles through every book. The publisher also groups several messages into one packet when they are produced within a microsecond or two, which cuts packet rate during bursts without adding meaningful delay.
8. Fairness
Fairness at an exchange means equal access, not equal outcomes. Equal cable lengths in the co-location hall, the same gateway hardware and software for every firm, and ordering by sequencer arrival are the basics. Some venues go further: IEX adds a fixed delay of about 350 microseconds to incoming orders using a coil of fibre, so that firms cannot react to price changes elsewhere before the exchange updates its own pegged orders. Others run frequent batch auctions instead of continuous matching. Mention these as design options with clear trade-offs, not as obvious improvements.
9. Opening and closing auctions
During the pre-open, orders collect without matching. At the open, the engine computes one clearing price for the whole book. For every candidate price, it adds up how much buyers will buy at or above it and how much sellers will sell at or below it, and picks the price where the executable quantity is largest. Ties are broken by the smallest leftover imbalance, then by closeness to a reference price such as the previous close. Every crossing order trades at that single price. The engine publishes indicative prices and imbalances during the pre-open so participants can react.
| Price | Buy interest at or above | Sell interest at or below | Executable |
|---|---|---|---|
| 182.40 | 9,500 | 3,200 | 3,200 |
| 182.45 | 7,800 | 6,100 | 6,100 |
| 182.50 | 6,400 | 7,300 | 6,400 |
| 182.55 | 4,100 | 8,800 | 4,100 |
In this example the auction prints at 182.50, the price where 6,400 shares can trade. The remaining 900 shares of sell interest rest in the book when continuous trading begins.
Partition instruments across single-threaded deterministic engines. Sequence and journal every input on two machines before acknowledging. Keep books in pre-allocated, cache-friendly arrays and lists. Run a standby on the same log, fence the old primary on failover, and publish market data once over multicast with A and B lines and sequence numbers for gap recovery.
07
Decisions and what they cost
Decisions and what they cost
No locks, no contention, completely predictable timing, and determinism comes for free. Throughput per partition is limited to what one core can do, which is still millions of operations a second.
Industry standardCan use more cores for one hot instrument, but locks add latency jitter and make replay non-deterministic, which breaks replication and audit.
Jitter and lost determinismConstant-time lookup of any level and excellent cache behaviour. Costs some memory for empty levels and needs a fallback when prices move far from the reference.
Fastest for typical instrumentsHandles any price range with modest memory. Pointer chasing adds cache misses on every lookup. A good first answer in an interview before you optimise.
Simpler, slowerAn acknowledged order survives any single machine failure. Adds a few microseconds for the replica round trip on a dedicated link.
Correctness over a few microsecondsShaves latency, but a crash in the gap can lose an event that was already matched and reported, leaving the market with a trade the exchange cannot reconstruct.
Unsafe for a venueRemoves several microseconds per hop and most of the variance. Requires specialised network cards and engineers who understand them.
Needed for this latency targetBusy polling, interrupt affinity and large buffers get latency down to tens of microseconds with ordinary tools. Acceptable for a venue that does not compete on speed.
Fine for slower markets08
Failure modes
Failure modes
A memory fault crashes the primary while it is processing the opening auction. Firms have orders in flight and some have received acknowledgements.
Every acknowledged order was journalled on two machines before release, and the standby has already applied it. The arbiter promotes the standby, bumps the epoch, and the publisher continues from the next sequence number. Firms see a pause of a few milliseconds, not lost orders.The output comparator finds that the standby produced a different fill quantity for one trade. Either a hardware fault corrupted memory or a code path is not deterministic.
Halt the affected partition immediately rather than guess which side is right. Replay the journal on a third machine to see which output is correct, then resume from the verified state. Treat any determinism bug as a severity one incident.A takeover announcement lands before the open and one instrument receives forty times its normal message rate. Its partition's queue grows and latency rises for every instrument sharing it.
Rebalance known hot instruments to lightly loaded partitions before the session, cap message rates per firm session, and enforce order-to-trade ratios so a single firm cannot flood the book with quotes it never intends to trade.A switch buffer overflows during a burst and a slice of multicast packets is dropped on the A line. Several firms see a gap in their sequence numbers.
The B line delivers the same packets through different hardware. For gaps on both lines, retransmission servers answer range requests, and the snapshot channel lets any subscriber rebuild a book from scratch.A trader types 2,000,000 shares instead of 2,000, priced 30% through the market. If it reaches the engine it sweeps the book and prints trades far from fair value.
Pre-trade risk rejects orders above the firm's maximum size or outside a price collar around the last trade. Price bands in the engine pause trading if prices still move too far, too fast, and a clearly erroneous trade policy covers the rest.A gateway's PTP sync fails and its timestamps drift by 200 microseconds. Regulatory reports now show orders apparently arriving out of order.
Ordering never depends on gateway clocks, only on the sequencer, so matching is unaffected. Monitor clock offset continuously and take a gateway out of service if it exceeds tolerance.09
Mistakes to avoid
Mistakes to avoid
Even an in-memory database adds transactions, locks and network hops that cost far more than the match itself.
Keep books in the engine's own memory and make the journal the durable record.Rounding errors mean two equal prices can compare as different, and money can appear or vanish.
Represent prices as integer ticks.Replays and standbys then produce different results, which breaks failover and audit.
Stamp time at the sequencer and keep the engine a pure function of its input.Allocation and garbage collection introduce pauses that show up as latency spikes at the worst moments.
Pre-allocate pools and ring buffers before the session opens.Thousands of streams diverge in timing and the slowest client's buffers back up into the publisher.
Use multicast with sequence numbers and a retransmission service.10
How to handle the interview
How to handle the interview
Start from latency and determinism. Say early that the data is small and the hard requirements are microsecond latency and identical replays. Every later choice follows from that.
Draw the sequencer before the engine. An interviewer wants to hear that a single sequenced input log defines the truth, and that replication and recovery are just replays of it.
Know your order book data structures. Price-level array or tree, FIFO list per level, hash map for cancels. Explain why cancels drive the design.
Defend the single thread. One core per partition, no locks, partition by instrument. Have a number ready for how many operations a core can do.
Explain failover with fencing. Standby on the same log, an arbiter, and an epoch that stops an old primary publishing. This is where many candidates get vague.
Cover market data properly. Multicast, sequence numbers, A and B lines, retransmission and snapshots. Mention that multicast makes the feed fair as well as efficient.
Mention risk controls. Pre-trade checks, price collars and trading halts show you know that an exchange protects the market, not just itself.
Do not over-claim on hardware. Kernel bypass and FPGAs are real, but say when they are worth it. A venue that does not compete on speed can skip them.
11
Growing the design in stages
Growing the design in stages
Get the sequenced log and deterministic engine right first. Every performance optimisation after that is safe, because you can replay real traffic to prove it changes nothing.
A single deterministic engine for all instruments, a journal on local disk plus a replica, and FIX gateways. Latency in the hundreds of microseconds, which is fine for many markets.
Instruments split across several engines, each with its own sequencer. A binary order entry protocol appears for latency-sensitive firms, and market data moves to multicast.
Gateways and engines adopt kernel-bypass networking, pinned cores and lock-free ring buffers. Median latency drops into the tens of microseconds.
Every engine gets a standby on the same log, an arbiter with epochs, and continuous comparison of outputs. Snapshots let a fresh standby join mid-session.
A disaster recovery site receives the journal in real time. Consolidated audit reporting and clock-sync rules apply. Every change now ships only after replaying a full real session and diffing outputs.
Watch and read
Try next
Free to read · better with landed
Whiteboard this with landed
landed runs it as a live system design round on the whiteboard and grades your trade-offs.
