Stock Trading Engine Interview Questions | JiQuest

add

#

Stock Trading Engine

System design deep dive · HLD

Design a Stock Exchange Matching Engine: full high-level design.

Requirements, back-of-envelope capacity estimation, a high-level design diagram, a deeper deployment architecture diagram, sequence diagrams for order submission and cancellation, and an entity-relationship diagram for the trading data model - with the reasoning an interviewer expects behind every box and arrow.

50K/secOrder msgs at peak
<500µsMatching latency p99
5,000Symbols, sharded
Tradersubmit order Matching engineprice-time priority Order bookin-memory, per symbol Trade executedfill confirmation Market data feedbroadcast L1/L2 same instant, to everyone

1. Clarify requirements before drawing any box

A matching engine is one of the few systems where "fast" and "fair" are both hard functional requirements, not just non-functional goals - regulators, not just users, care about the ordering guarantees. Scope has to be pinned down before any box gets drawn.

Functional requirements

Submit orderAccept limit and market orders (buy/sell, symbol, quantity, price, time-in-force) over a FIX or REST gateway.
Match ordersMatch an incoming order against resting orders using strict price-time priority, per symbol.
Cancel / replaceCancel or modify a resting order before it is fully filled, without disturbing other resting orders' priority.
Broadcast market dataPublish best bid/ask (L1), depth (L2), and trade prints to every subscriber at the same instant.

Non-functional requirements

Deterministic low latencyMatching must complete in well under a millisecond p99, with bounded worst-case jitter.
Strict fairnessTwo orders at the same price must fill in exactly the sequence they arrived - reordering is a compliance violation.
Zero data lossEvery accepted order and trade must survive a crash and be replayable to an identical book state.
Simultaneous market dataNo subscriber may legally see a price update before another subscriber.
Explicitly out of scope Clearing and settlement ledger reconciliation with a central counterparty, margin/collateral calculation, and cross-asset risk netting are downstream systems that consume this engine's trade events asynchronously - they are called out in the deep-dive section as consumers, not as core matching-engine scope.

2. Back-of-the-envelope capacity estimation

These numbers rule out entire categories of design before a single box is drawn: the 500-microsecond matching budget alone eliminates any shared lock, any network round trip, and any general-purpose database from the hot path.

MetricAssumptionResulting estimate
Order messages (new + cancel + replace)5,000 listed symbols, ~40M messages/day~460 msg/sec average, ~50,000 msg/sec peak at the 9:30am open
Trades executed~15% of order messages result in a fill~6M trades/day, ~7,500 trades/sec at peak
Matching latency budgetEnd-to-end order-to-ack SLA of 1msMatching itself must finish in <500µs p99, leaving headroom for network + risk checks
Order book memory footprint5,000 symbols × ~400 resting orders × ~96 bytes/order~192MB - fits entirely in RAM on a single matching shard
Market data fan-out50,000 subscriber connections × 7,500 trades/sec × ~200 bytes~10.5 Gbps aggregate at peak - requires multicast, not per-subscriber TCP
Why this matters The 500-microsecond matching budget is the single number that forces single-threaded, in-memory, per-symbol order books with lock-free hand-off - any design that puts a lock, a network hop, or a database write on the synchronous matching path cannot hit this number, no matter how the rest of the system is built.

3. High-level design (HLD)

The HLD names the major components and the one-directional flow of an order from submission to trade and market data, without yet committing to core-pinning, replica counts, or failover mechanics - that belongs in the architecture diagram.

Traderbroker / algo Order gatewayFIX session, validate Risk checkbuying power, limits Sequencerglobal seq per symbol WAL / journal Matching enginein-memory order book Exec reportsfill / ack to gateway Market data pubmulticast L1/L2 Clearing queueasync downstream
Stateless gate-keepingFast-path in-memory matchingExecution reportingAsync / broadcast

What each box owns

Order gateway (entry point)

Terminates the FIX session, validates message syntax and symbol tradeability, and normalizes the order into an internal representation. It is kept stateless so any gateway instance can serve any trader without sticky sessions - all per-order state lives downstream.

Risk check (pre-trade gate)

Synchronous checks against an in-memory account-limits cache: available buying power/margin, per-order and per-account fat-finger limits (max notional, max quantity), and self-trade prevention flags. Rejects return straight to the trader without ever reaching the matching engine, so a bad order never costs the book any time.

Sequencer + matching engine

The sequencer stamps every accepted order and cancel with a strictly increasing per-symbol sequence number before handoff - this is what makes WAL replay deterministic. The matching engine owns one in-memory order book per symbol, pinned to a single core, so two messages for the same symbol can never be processed concurrently and no lock is ever needed.

WAL, exec reports, and market data

Every accepted order and resulting trade is appended to a write-ahead log before any acknowledgment leaves the engine, so a crash can always be replayed to an identical book state. Exec reports return the fill confirmation to the originating gateway; the market data publisher fans the same trade and depth update out to every subscriber over multicast at the same instant, satisfying the simultaneous-access requirement.

4. Detailed architecture diagram

The architecture diagram answers "how is this actually deployed?" - and for a matching engine the most interview-relevant answer is what it deliberately does not do: it is not multi-region active-active, because speed-of-light latency differences between regions would themselves be a fairness violation.

Colocation access layer (single facility) FIX gateways ×20 Risk check svc ×12 Sequencer (primary + hot backup) Drop copy / audit tapregulatory record Matching shard group A (symbols A-M) 2,500 cores, 1 symbol/core sync WAL ship to hot standby NVMe WAL store (mirrored) Book snapshot writer (periodic) Matching shard group B (symbols N-Z) 2,500 cores, 1 symbol/core sync WAL ship to hot standby Market data publisherUDP multicast, same facilitygap-fill via TCP retransmit Durability tier Hot standby cores Exec report bus failover promotes standby with zero committed-order loss Downstream / async Kafka topic Clearinghouse feed trade events replicated for settlement, T+1 DR site (warm, different city) Async WAL replica activated only on total primary-site loss
DecisionChoiceReasoning
Multi-regionSingle primary colocation facility, cold/warm DR site onlyActive-active regions would let participants near one region see and react to a price move before participants near the other - a fairness/regulatory problem, not merely a latency one.
Sharding keySymbol (ticker), pinned to one coreMatching for a given symbol must be strictly sequential; sharding by symbol means no cross-shard coordination is ever needed to process a single order.
FailoverSynchronous WAL replication to a hot standby core, same facilityAn asynchronous standby could lose the last few trades on failover; synchronous replication trades a small amount of latency for zero data loss.
Market data deliveryUDP multicast + gap-fill retransmission serverMulticast delivers one packet to every subscriber at the same instant, satisfying simultaneous-access rules; TCP is used only to fill gaps a subscriber detects via sequence-number holes.

5. Sequence diagrams for the two critical flows

This is where an interviewer checks whether the ordering guarantees are real: what happens when a cancel and a fill race each other, and whether the design can prove - not just claim - that replay always produces the same outcome.

5.1 Order submission and match

Trader Gateway Risk check Matching engine Market data 1. NewOrder(BUY 100 AAPL @189.50) 2. checkBuyingPower() 3. approved 4. submit(order) - sequencer stamps seq# 5. WAL append, then match vs resting sell 6. ExecutionReport(FILLED, 100@189.50) 7. publish trade print + book update 8. Fill confirmed to trader

Step 5 happens entirely inside the matching engine's single core for that symbol - the WAL append and the in-memory match are the only work on the hot path, with no lock and no network hop. Step 7 is drawn as a separate, non-blocking arrow because market data subscribers are not part of the response path back to the trader, but unlike an analytics pipeline it still fires within microseconds - "async" here means "not on this response," not "eventually."

5.2 Cancel racing a fill (sequence-number resolves the race)

Trader Gateway Sequencer Matching engine WAL 1. CancelRequest(order 123, qty 100) 2. submit cancel 3. assigns seq#501 (a resting sell already matched order 123 at seq#500) 4. process seq#500: fill 60 shares of order 123 5. append fill, then cancel, in seq order 6. process seq#501: cancel remaining 40 7. PartialFill(60) + CancelAck(remaining 40) 8. 60 filled @189.50, 40 canceled

The cancel and the fill arrived at the matching engine at almost the same instant, but the sequencer had already stamped the incoming sell order as seq#500 and the cancel as seq#501 - so the engine processes them in that exact order, filling 60 shares first and canceling only the remaining 40. Replaying the WAL from scratch always reproduces this exact outcome, which is precisely what a post-trade audit or regulator re-check requires: the ordering is a property of the sequence number, not of wall-clock timing at the engine.

6. Entity-relationship (ER) diagram and schema

The data model has to answer three questions: what is authoritative for a resting order's remaining size, how is the immutable trade audit trail kept separate from mutable order state, and how is the L2 depth view served without querying the live in-memory book.

orders PK order_id BIGINT symbol VARCHAR side ENUM price,qty DECIMAL remaining_qty DECIMAL status ENUM sequence_num BIGINT FK account_id BIGINT trades PK trade_id BIGINT symbol VARCHAR FK buy_order_id BIGINT FK sell_order_id BIGINT price,qty DECIMAL sequence_num BIGINT executed_at TIMESTAMP immutable (append-only) order_book_levels PK symbol,side,price aggregate_qty BIGINT order_count INT last_updated TIMESTAMP 1N derived one order fills into many trades (partial fills); order_book_levels is rebuilt from resting orders, not FK-enforced

Key modeling decisions

orders.remaining_quantity is mutated in placeUnlike trades, an order row is the authoritative live state of a resting order and is updated on every partial fill or cancel.
trades is strictly append-only and immutableIt is the regulatory audit trail; rows are never updated or deleted, and sequence_num makes replay order reproducible.
order_book_levels is a rebuildable materialized viewIt exists purely to serve L2 snapshot requests and crash recovery - the real source of truth is the in-memory book plus the WAL.
sequence_num is denormalized onto both orders and tradesEvery persisted row carries the global per-symbol sequence number so the store can be replayed and cross-checked in exact arrival order.
Storage choiceUse whenWatch out for
Relational (Postgres, sharded by symbol), for orders/tradesYou need transactional guarantees on order-state transitions and joins for compliance/audit reporting.The relational store is never on the matching hot path - it's fed asynchronously from the WAL, so it can lag the in-memory book by milliseconds.
Wide-column / time-series store (kdb+, Cassandra) for trade historyQuery pattern is append-heavy time-series analytics (VWAP, historical replay) at extreme volume.Cross-symbol joins and strong consistency are harder to express; kdb+ is the de facto industry choice specifically for this workload.

7. Deep dives interviewers actually probe

Why keep the order book fully in-memory and single-threaded per symbol, instead of a shared database table?

Any lock or round trip to a shared store on the hot path adds latency measured in the wrong units - matching must resolve in microseconds, while disk or network I/O is milliseconds. Pinning each symbol's book to one core removes the need for any lock at all, since there is no concurrent writer to coordinate with by construction. Durability comes from the WAL append, not from the book itself being persistent.

How does price-time priority actually get implemented as a data structure?

Each side of the book (bid/ask) is a price-indexed structure such as a red-black tree or skip list keyed by price, with the best price at one end. Each price level holds a FIFO queue of resting orders in arrival order. An incoming order walks the opposite side outward from the best price, filling FIFO at each level until exhausted or price no longer crosses; a new order joining an existing price level is appended to the tail of that level's queue, never inserted ahead of resting orders.

// simplified match loop for an incoming BUY order
while (order.remainingQty > 0 && askSide.bestPrice() <= order.price) {
    PriceLevel level = askSide.bestLevel();
    Order resting = level.queue.peekFirst(); // FIFO within the level
    long fillQty = Math.min(order.remainingQty, resting.remainingQty);
    emitTrade(order, resting, level.price, fillQty);
    resting.remainingQty -= fillQty;
    order.remainingQty -= fillQty;
    if (resting.remainingQty == 0) level.queue.pollFirst();
}

What happens to market data fairness if one subscriber's network is faster than another's?

The exchange guarantees simultaneous publish - a single multicast send - not simultaneous receipt; regulators require the source to be fair, not the last mile. This is exactly why market data goes out over UDP multicast rather than per-connection TCP: every subscriber receives the same packet the instant it leaves the publisher, and any latency differential afterward is the subscriber's own colocation and hardware problem - which is precisely why HFT firms pay to rack servers physically close to the exchange.

How do you prevent one misbehaving order from stalling the whole engine?

Pre-trade risk checks (buying power, max notional, max order size, self-trade prevention) run before an order ever reaches the matching engine, rejecting dangerous orders synchronously and cheaply. Because each symbol is isolated on its own core, even a pathological order that slips through - an accidental oversized market order - can only ever affect that one symbol's book and its own subscribers; it cannot stall matching for any other symbol.

What is the single biggest bottleneck as message volume scales 10x during a volatility spike?

Not the matching computation itself - that stays effectively O(log n) per order and symbols are already isolated. The real bottleneck is correlated bursts across many symbols at once (a market-wide circuit-breaker event), which stresses the sequencer tier and the market-data network's bandwidth headroom simultaneously - solved by scaling sequencer capacity per shard group and ensuring the multicast network itself, not just the publisher process, has enough spare bandwidth so a slow consumer on a shared segment can never create backpressure on the publisher.

8. Summary: what a strong answer covers

Treated fairness as a hard requirement, not a nice-to-haveJustified every latency numberChose single-threaded per-symbol books over shared locks Explained deterministic sequencing and WAL replayMade market data simultaneous by constructionRejected multi-region active-active on purpose
Interview tip The strongest signal in a trading-system interview is explaining why the design is lock-free, sequenced, and replayable not just for speed but so that every trade is reproducible and auditable after the fact - fairness and determinism are functional requirements here, on equal footing with raw latency.
No comments
Leave a Comment