Stock Exchange System Design Interview: Building a Microsecond Order Matching Engine
A modern equities exchange matches orders in single-digit to low tens of microseconds, wire to wire, and never blinks during a market open. LMAX published that its single-threaded business logic core handles 6 million orders per second on one commodity server. Nasdaq's INET platform is described as processing over a million messages per second with 99.99th percentile latency under 100 microseconds. The hard part is not the average case. It is holding that latency steady when a Fed announcement triggers a burst of cancels and the book churns thousands of times per millisecond.
A stock exchange is a low-latency deterministic matching system, not a CRUD app. Orders arrive over gateways, get validated and risk-checked, then flow into a single sequenced input stream that assigns each event a monotonic sequence number. A single-threaded matching engine consumes that stream and applies each order to an in-memory limit order book, which keeps resting orders sorted by price and, within a price level, by arrival time. Matching follows price-time priority: the best price trades first, and ties break by who got there earliest. Because every replica consumes the exact same ordered input and the engine is deterministic, all replicas compute byte-identical output, so recovery and hot standby become replay problems rather than distributed-consensus problems on the hot path. Durability comes from journaling the sequenced input to an append-only log before or in parallel with matching, in the style of the LMAX Disruptor. Fills and book changes fan out as a market data feed with periodic snapshots plus incremental deltas. The design goal is microsecond-scale, jitter-free matching with zero data loss and provable determinism.
Asked at: Asked at high-frequency trading firms, exchanges, and market-data vendors like Jane Street, Citadel Securities, Nasdaq, Jump Trading, and IMC, and increasingly at crypto exchanges building spot and derivatives order books. It also shows up at any firm that runs a latency-critical event-processing core.
Why this question is asked
This problem separates candidates who reach for a database and a queue from candidates who understand mechanical sympathy. It tests whether you can reason about a hot path where a lock, a cache miss, or a garbage-collection pause is a correctness-visible event, not a micro-optimization. Interviewers want to see you pick the right data structure for the order book, explain price-time priority precisely, and defend a single-threaded design against the instinct to parallelize. Above all it probes determinism: can you explain why one sequenced input stream plus a deterministic state machine gives you replicas, recovery, and audit for free, and why introducing nondeterminism, like reading wall-clock time inside the match loop, quietly breaks all three.
Requirements
Always clarify these in the first 5 minutes of the interview. Do not start drawing boxes until both lists are agreed.
Functional requirements
- Accept limit and market orders, plus cancel and modify (cancel-replace) requests, per instrument.
- Maintain a limit order book per instrument with two sides, bids and asks, sorted by price and then by arrival time.
- Match incoming orders against resting orders using strict price-time priority and generate fills (executions).
- Support core order types and time-in-force: day, immediate-or-cancel (IOC), fill-or-kill (FOK), and post-only.
- Assign every accepted order and cancel a unique monotonic sequence number and process events in sequence order.
- Emit an execution report for every order lifecycle event: accepted, partially filled, filled, cancelled, rejected.
- Publish a market data feed: top-of-book and full-depth snapshots plus incremental updates, and a public trade tape.
- Persist the sequenced input stream durably so the exact book state can be reconstructed by replay.
- Enforce pre-trade risk and validation: price bands, lot sizes, self-trade prevention, and credit or position limits.
Non-functional requirements
- Wire-to-wire matching latency in single-digit to low tens of microseconds at the median, with bounded tail latency.
- Deterministic execution: identical input sequence must always produce identical fills and book state on any replica.
- Zero acknowledged-order loss: an order acknowledged to the client must survive process or machine failure.
- High throughput per instrument core, on the order of millions of events per second sustained, with headroom for bursts.
- Fairness: no participant is systematically favored beyond what price-time priority dictates.
- Fast recovery: a failed engine must resume from the log or a hot standby in well under a second.
- Strict auditability: every state transition is reconstructable and timestamped for regulators.
Back-of-envelope scale estimates
Show your math. Pulling numbers from thin air signals you have not thought about the load.
Peak input rate per matching core
~1-6 million events/sec
LMAX published 6 million orders/sec on a single business-logic thread, and Nasdaq INET is described at 1M+ messages/sec. A production instrument core is designed for the low millions per second so a single hot symbol at the open does not saturate it. Sourced upper bound, Derived design target.
Per-event latency budget
~1-10 microseconds in-engine
If wire-to-wire is under ~100 microseconds and most of that is NIC, kernel-bypass, serialization, and gateway hops, the matching step itself must live in the low microseconds. That leaves no room for a lock wait (hundreds of ns to us) or a minor GC pause (ms). Derived from published wire-to-wire figures.
Journal write bandwidth
~200-600 MB/s sequential per core
At 2M events/sec and ~100-300 bytes per sequenced input record, that is 200-600 MB/s of append-only writes. Sequential writes to NVMe or a battery-backed device easily sustain this, which is why LMAX journals to raw sequential files rather than a database. Derived (2M x 100-300B).
Active order book depth per instrument
~10^3-10^5 resting orders
A liquid equity can hold tens of thousands of resting orders across a few thousand price levels. The book must fit comfortably in L2/L3-friendly in-memory structures. At ~64 bytes per order node, 100K orders is ~6 MB, small enough to stay resident and cache-warm. Derived estimate.
Market data fan-out
10^3-10^4 subscribers per feed
Co-located members, data vendors, and internal risk systems all subscribe to the incremental feed. Fan-out is handled by reliable multicast or a broker tier downstream of matching, not by the engine looping over subscribers, so publish cost is O(1) from the engine's view. Derived estimate.
Daily sequenced-event volume
~1-10 billion events/day
A busy US equities day is billions of order, cancel, and trade messages across all symbols. At even 2 billion events at ~200 bytes, the day's journal is ~400 GB, which drives snapshot cadence so restart replay stays under a minute. LMAX reported full restart with a day's replay in under one minute. Derived, with sourced restart figure.
High-level architecture
An order enters over a co-located gateway speaking a binary protocol such as FIX/FAST or a proprietary format, arriving via a kernel-bypass NIC so the packet skips the OS network stack. The gateway does stateless work: authenticate the session, decode the message, run cheap pre-trade validation like price bands and lot size, and stamp a client-level sequence for gap detection. It then hands the order to the sequencer, which is the heart of the design. The sequencer serializes all inputs for a given instrument (or instrument shard) into one totally ordered stream and assigns each a monotonic sequence number. That sequenced record is written to an append-only journal and replicated to standby nodes before the client sees a firm acknowledgment, which is what makes an acknowledged order durable. A single matching thread consumes the sequenced stream in order and applies each event to the in-memory limit order book: it walks the opposite side of the book from the best price inward, generates fills under price-time priority, updates or removes resting orders, and inserts any residual quantity as a new resting order. Every fill and book mutation is written to an output stream. Downstream, execution reports route back to the originating members and a market data publisher turns book mutations into snapshot plus incremental feeds and a public trade tape. Because the engine is a deterministic function of the ordered input, a hot standby that replays the same stream holds an identical book and can take over in microseconds, and any node can be rebuilt by loading the last snapshot and replaying the journal from there.
In a real interview, sketch this on the whiteboard before diving into any single box.
Core components
Walk through each service. The interviewer wants to hear what each one owns, not just the names.
Order Entry Gateway
Terminates member sessions and decodes the wire protocol using kernel-bypass networking so packets avoid the OS stack. It performs stateless, cheap validation (session auth, symbol lookup, price band, lot size, rate limits) and forwards well-formed orders to the sequencer. Keeping it stateless lets you run many gateways in parallel in front of one matching core.
Sequencer
Assigns a single monotonic sequence number to every input event for an instrument, producing the one totally ordered stream that the whole design depends on. It is the single writer to the input log, which removes contention and gives every replica the same replayable order. This is the component that turns a distributed problem into a deterministic replay problem.
Matching Engine
A single-threaded, in-memory state machine that consumes the sequenced stream and applies price-time priority matching against the order book. Single-threaded is a feature: it removes locks, keeps the hot data in CPU cache, and guarantees deterministic output. It reads no wall-clock time or random source inside the match loop so replicas stay byte-identical.
Limit Order Book
The in-memory data structure holding resting orders on both sides, organized as price levels sorted best-first, with a FIFO queue of orders at each level to enforce time priority. Common implementations use a sorted map or array of price levels plus intrusive doubly linked lists per level, and a hash index from order id to node for O(1) cancels. Best bid and best ask are cached for constant-time top-of-book reads.
Journal and Replication Layer
Streams the sequenced input to an append-only log on fast storage and to standby nodes before the order is acknowledged, giving durability and hot failover. Sequential append is the only disk pattern that keeps up at these rates, which is why the log is a raw file, not a relational database. Recovery loads the newest snapshot and replays forward from its sequence number.
Market Data Publisher
Converts book mutations into an outbound feed: full or depth snapshots on a schedule plus incremental deltas for every change, and a separate trade tape. It fans out via reliable multicast or a downstream broker tier so the engine itself does O(1) work per event. Sequence numbers on the feed let subscribers detect gaps and re-request a snapshot.
Snapshot and Recovery Service
Periodically serializes the full book and engine state during quiet windows so that replay on restart is bounded. The snapshot plus the journal tail equals the exact current state, which is the event-sourcing guarantee. Snapshot cadence is tuned so a cold restart, including replaying the remaining log, completes in under a minute.
Data model
Pick the right store per table. Justify each choice with the access pattern, not by reflex.
orders (in-memory node)order_idinstrument_idsidepriceremaining_qtyarrival_seqThe resting-order record. arrival_seq (the sequencer's number) is the time-priority tiebreak within a price level. Nodes are pooled and reused to avoid allocation on the hot path. A hash index maps order_id to node for O(1) cancel and modify.
price_levels (per instrument, per side)instrument_idsidepricehead_ordertail_orderaggregate_qtyOne entry per price with a FIFO list of orders (head to tail = oldest to newest). aggregate_qty supports fast depth publishing. Levels are held in a structure sorted best-first so the engine reaches the top of book in constant or near-constant time.
input_journal (append-only)global_seqevent_typeinstrument_idpayloadwall_clock_tsThe durable source of truth: every accepted order, cancel, and modify in sequence order. Append-only and never mutated. wall_clock_ts is recorded here for audit but is NOT read by the deterministic match loop, so replay is reproducible.
executions (output stream)exec_idglobal_seqaggressor_order_idresting_order_idtrade_pricetrade_qtyOne record per fill. trade_price is the resting (passive) order's price under standard matching. Feeds execution reports to members and the public trade tape. Derived entirely from replaying the input, so it can be regenerated exactly.
instrumentsinstrument_idsymboltick_sizelot_sizeprice_bandmatching_shardReference data controlling validation and routing. tick_size and lot_size gate order acceptance; price_band sets the daily limit-up/limit-down window. matching_shard maps the symbol to the engine core that owns its book.
md_snapshotsinstrument_idsnapshot_seqbidsaskscreated_atPeriodic serialized book state keyed by the sequence number it reflects. Late-joining or gapped market-data subscribers load the newest snapshot then apply incrementals from snapshot_seq forward. Also used to bound engine restart replay.
Deep dives
These are the conversations the interviewer is steering you toward. Practice each one until you can talk through it without notes.
The limit order book data structure and price-time priority
The book must answer three operations fast: insert a resting order, cancel or modify an existing one, and match an aggressor against the best available prices. The standard design keeps price levels sorted best-first, and at each level a FIFO queue of orders so the earliest arrival trades first. A good implementation combines a sorted structure over price levels (a balanced tree, a skip list, or for tick-bounded instruments a direct-indexed array of levels) with intrusive doubly linked lists per level and a hash map from order id to node. That gives O(1) best-bid and best-ask reads because you cache the top pointers, O(1) cancel because the hash map jumps straight to the node and you unlink it, and matching that walks price levels from the top inward. Price-time priority is the rule: the best price wins, and among orders at the same price the oldest (lowest arrival sequence) fills first. Matching an aggressor means repeatedly taking the head order at the best opposing level, generating a fill for the min of the two remaining quantities, and advancing until the aggressor is exhausted or the price no longer crosses. Any leftover quantity from a limit order rests in the book at its limit price.
Sequencing and determinism: one input stream, identical replicas
The single most important architectural decision is to funnel every input for an instrument through one sequencer that assigns a monotonic number and produces a single totally ordered stream. Once inputs are ordered and the matching engine is a pure deterministic function of that order, three hard problems collapse into one. Replication becomes: ship the ordered stream to standbys and let them replay it, and they will hold byte-identical books. Recovery becomes: reload a snapshot and replay the journal tail. Audit becomes: the journal already is the complete, ordered, reproducible history. The catch is that determinism is fragile. The match loop must never read wall-clock time, never call a random generator, never depend on map iteration order or thread scheduling, and never let a floating-point rounding difference creep in. Anything that varies between two runs of the same input breaks replica equivalence silently, and you find out during a failover in production. So time, sequence ids, and any external inputs are captured into the stream by the sequencer and then read as data, not sampled live inside the engine.
Why single-threaded matching beats a parallel design
The instinct is to parallelize for throughput, but for a single instrument's book that is usually the wrong call. A shared order book protected by locks pays lock acquisition, contention, and cache-line ping-pong between cores on every mutation, and it reintroduces nondeterminism through scheduling. LMAX found that when they prototyped actor and staged (SEDA) designs, the processors spent more time managing queues than doing real work, and queue contention wrecked CPU caching. A single thread that owns the whole book keeps the hot working set in L1/L2 cache, needs no locks, and runs a tight predictable loop, which is exactly what you want for low tail latency. You still get horizontal scale, just at a coarser grain: shard by instrument. Each symbol or group of symbols is owned by one core with its own sequencer and journal, and thousands of these run in parallel across a fleet. Cross-instrument operations are rare on the matching hot path, so this sharding is clean. The rule of thumb is parallelize across books, stay single-threaded within a book.
In-memory matching with an event log for durability (LMAX Disruptor pattern)
Holding the book in memory is what makes microsecond matching possible, but memory is volatile, so durability comes from an append-only event log rather than from writing book state to a database. This is the LMAX pattern: the business logic runs entirely in RAM surrounded by ring buffers, and durability is provided by journaling the sequenced input. The order of operations matters. The sequenced record is appended to the journal and replicated to standbys, and only then is the client sent a firm ack, so an acknowledged order is guaranteed recoverable. The engine can process the event in parallel with the journal write because both consume the same ordered stream; the ack just waits for the durability barrier. Sequential append is the only disk access pattern fast enough here, which is why the journal is a raw file and writes are batched. Because the log plus periodic snapshots fully determine state, you can rebuild any node, spin up a new replica, or replay a trading day in a test environment for debugging, all from the same artifacts. LMAX reported that a full restart, including JVM start, loading a snapshot, and replaying a day of events, ran in under a minute.
Fighting latency: kernel bypass, busy-spin, and no GC pauses
At single-digit-microsecond budgets, the enemies are the operating system and the memory allocator. Kernel-bypass networking (technologies like DPDK or Solarflare Onload) lets the application read packets directly from the NIC in user space, skipping the kernel's network stack and the context switches it costs. The matching thread is typically pinned to a dedicated core with interrupts and other work steered elsewhere, and it busy-spins on its input rather than blocking on a condition variable, trading CPU cycles for the elimination of wake-up latency and scheduler jitter. Memory is managed to avoid allocation on the hot path entirely: order nodes come from preallocated pools and are recycled, so the garbage collector (in a JVM system) or the allocator (in C++) is never triggered mid-match. A single stop-the-world GC pause of even a few milliseconds is thousands of times the entire latency budget and would show up as a nasty tail spike, so JVM-based engines tune for zero-garbage steady state or use off-heap memory, and C++ engines avoid malloc in the loop. The theme is mechanical sympathy: design with the cache, the branch predictor, and the memory subsystem rather than against them.
Market data fan-out: snapshots plus incremental updates
Every book mutation must reach subscribers, but the engine cannot afford to loop over thousands of them. The pattern is to emit a compact incremental update per change (level added, quantity changed, level removed, plus the trade tape) onto a reliable multicast group or a downstream broker tier that handles fan-out, so the engine does O(1) work per event. Subscribers cannot rely on incrementals alone: a client that joins late or drops a packet needs a way to resynchronize. That is what snapshots are for. A separate service publishes periodic full or depth-limited snapshots stamped with the sequence number they reflect, and every incremental carries a sequence number too. A subscriber recovers by grabbing the latest snapshot, then applying incrementals whose sequence is greater than the snapshot's, discarding older ones. Gap detection falls out of the monotonic sequence: a hole in the numbers tells the subscriber it missed data and must re-snapshot. Depth is often tiered, top-of-book as one lightweight feed and full depth as a heavier one, so latency-sensitive consumers subscribe only to what they need.
Failover and correctness under load without losing determinism
Because replicas are deterministic replays of one stream, failover is fundamentally different from a typical stateful service. A hot standby consumes the same sequenced, journaled stream as the primary and maintains an identical book a few events behind. On primary failure, the standby is promoted; it already has the state, so it only needs to confirm it has consumed through the last durably replicated sequence number before it starts accepting new input. The durability barrier (journal plus replicate before ack) guarantees no acknowledged order is lost across the switch, and the sequence numbering guarantees no order is processed twice or out of order after promotion. The genuinely hard part is behavior under a burst, like a news-driven cancel storm, where input rate spikes. Because the engine is a fixed-cost deterministic loop, its per-event latency stays stable, but the input queue can grow, so you monitor queue depth and apply admission control and rate limits at the gateway rather than letting the match loop degrade. You never shed load by dropping already-sequenced events, because that would break the replay contract. Backpressure lives in front of the sequencer, not behind it.
Trade-offs to discuss
Every senior interviewer expects you to surface at least 3 of these. Pick the decisions, state the alternatives, and justify your choice.
Single-threaded matching per book versus a lock-based parallel book
One thread owning the book gives lock-free execution, cache locality, deterministic output, and low tail latency. A parallel book pays lock contention and cache-line bouncing and reintroduces nondeterminism. You recover throughput by sharding across instruments, not by threading within one book.
In-memory state with an event log versus a database of record
Memory plus an append-only journal delivers microsecond matching and full reconstructability. A database on the hot path adds milliseconds and cannot keep up. The cost is that you own snapshotting, replay, and recovery logic yourself instead of leaning on a DBMS.
Kernel bypass and busy-spin versus the standard OS stack and blocking I/O
Bypass and spinning remove context switches and wake-up jitter, which is the difference between microseconds and tens of microseconds. The price is a fully consumed CPU core per engine, more complex operations, and specialized NIC hardware and drivers.
Journal and replicate before acking versus acking immediately
Confirming durability before the ack guarantees no acknowledged order is lost on failure, which is non-negotiable for an exchange. It adds a small, bounded latency to acknowledgment. Acking first would be faster but could lose a client's order on a crash, which is unacceptable.
Strict price-time priority versus pro-rata or size-priority matching
Price-time is simple, transparent, and rewards being early, which most equity venues use. Pro-rata allocation (common in some futures) can encourage tighter quoting and larger resting size. The choice changes participant behavior and complexity, so you pick per market and state it explicitly.
Shard by instrument versus one giant global engine
Per-instrument shards scale horizontally, isolate a hot symbol's load, and keep each book small and cache-warm. A single global engine is simpler operationally but becomes a throughput ceiling and a blast radius. Cross-instrument logic (like some risk checks) then has to coordinate across shards.
How Stock Exchange actually does it
The canonical public reference is the LMAX architecture, documented by Martin Fowler and by the LMAX team's own Disruptor paper. LMAX runs all business logic on a single in-memory thread, the Business Logic Processor, surrounded by input and output Disruptors, which are lock-free ring buffers, and reported 6 million orders per second on one thread with event-sourced durability and sub-minute restart via snapshot plus replay. The Disruptor paper reports over 25 million messages per second and latencies under 50 nanoseconds for inter-thread handoff on moderate hardware. Nasdaq's INET matching platform is widely described as processing over a million messages per second at 99.99th percentile latency under 100 microseconds, using kernel-bypass user-space networking and, in some paths, hardware acceleration. Martin Thompson's mechanical sympathy writing and the Aeron messaging project extend the same ideas of single-writer, cache-aware, allocation-free design that underpin low-latency exchanges. These sources describe the general architecture; exact numbers for any specific live venue are proprietary and the figures cited here are the publicly published ones.
Lessons to study before this interview
If any of these topics are fuzzy, the interviewer will catch it. Each lesson is 15 to 60 minutes with diagrams, code, and a quiz.
Related system design interview questions
Practice these next. They lean on the same core building blocks as Stock Exchange.