Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

System Design

Design a Stock Exchange

Step 1 / 9

Learn system design by building a stock exchange / matching engine step by step.

The numbers to beatpricethen timeFIFOper leveldeterministicfills

The whole design, in writing

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

What is a stock exchange?

Buyers and sellers submit orders; the exchange matches them into trades — fairly, deterministically, and in microseconds. Money is on the line, so it must never lose an order, never mismatch, and treat every participant equally. Correctness and latency, both absolute.

Traderplace order
New in this step: Trader.

At the center is a matching engine running a single, ordered stream of events against an in-memory order book. Wrap it in admission, risk checks, deterministic sequencing, market-data broadcast, and a durable journal — keeping the core path tiny and fast.

What the new pieces do

Traderclient
Sends buy/sell orders and expects acknowledgement and fills in microseconds. Latency and fairness are the product — a slow exchange loses everyone.

Step 1 · The core

Match orders into trades

A trader sends “buy 100 at $50”. The exchange must instantly find a matching sell and execute, or rest the order until one appears. What structure makes that match fast and correct?

Order GatewayMatching EngineOrder Book
New in this step: Order Gateway, Matching Engine, Order Book. · swipe to pan the diagram

"Buy 100 at $50" must instantly find a match or wait for one. What structure makes that fast and correct?

  1. Scanning every open order per incoming order is O(N) on the hottest path in the system — far too slow for microsecond matching. You need a structure sorted for instant best-price lookup.

  2. A round-trip to disk-backed storage per order is orders of magnitude too slow, and SQL can’t express price-time matching cheaply. The book must live in memory, purpose-built.

  3. Best bid and best ask sit at the top; matching crosses the spread — highest bid against lowest ask — until they no longer overlap. A market order takes best price; a limit order rests if it can’t match.

An Order Gateway admits the order to a Matching Engine that runs against an Order Book — resting bids and asks per symbol. A market order takes the best price; a limit order matches if possible, otherwise rests in the book.

Why this piece earns its place

The structure above answers the arrival of a new order, but most of what actually hits an exchange is not a new order — it is a cancel or an amend, sent by market makers re-quoting as the price moves. If the book is only sorted queues of resting orders, a cancel has to walk the queue at that price level to find its order, so the one path that exists to relieve pressure becomes the slowest thing in the system exactly when quoting is heaviest. The book is therefore two structures kept in step: the price levels with their queues, and an order id → queue node index that lets a cancel unlink in constant time. The second thing this step hides is memory. Resting limit orders priced far from where trading is happening never match and never leave on their own, so the book grows through the session with orders that contribute nothing to any fill. Size the structure for the depth resting at the close, not for the rate at which trades occur — they are very different numbers.

What the new pieces do

Order Gatewaybackend
Authenticates the trader, validates the order, and admits it into the system. The boundary between the messy outside world and the pristine matching core.
Matching Engineservice
The heart: matches incoming orders against resting ones by price, then time. Kept single-threaded and in-memory so it’s deterministic and blazingly fast.
Order Bookstore
The live in-memory book of resting buy (bid) and sell (ask) orders per symbol, sorted for instant best-price lookup and matching.

Step 2 · The rules of matching

Price-time priority

When many orders want the same price, who trades first? Get this wrong and the market is unfair — and traders will (rightly) leave an exchange that doesn’t treat equal orders equally.

TraderOrder GatewayMatching EngineOrder Book
The system as it stands at this step. · swipe to pan the diagram

Many orders want the same price. Who trades first, fairly?

  1. Favoring size lets big players jump the queue and punishes small orders — manifestly unfair, and traders will leave. Fairness can’t depend on order size.

  2. Best price wins; among equal prices the earliest order fills first. Organizing the book as price levels, each a FIFO queue, builds the rule into the data structure — deterministic and auditable.

  3. Randomness makes fills unpredictable and unauditable — a trader can’t reason about where they stand in line. Matching must be deterministic, so equal-priced orders need a stable order: time.

Match by price-time priority: best price wins, and among equal prices the earliest order fills first. The book is organized into price levels, each a FIFO queue, so the rule is built into the data structure.

Why this piece earns its place

Price-time priority is a promise about what can and cannot move a trader in the queue, and every feature added after this step is a chance to break that promise quietly. The moment order modification is on the table the rule needs an explicit answer: increasing size, or changing price, has to send the order to the back of its new level — otherwise a trader holds a spot at the front and keeps re-using it, which is queue-jumping with extra steps. Only reducing size can keep priority, because it takes nothing from anyone behind. The same question returns for hidden or iceberg size, for self-match prevention, and for any order type that gets to skip a queue. My rule is that each of those has to state its effect on priority in the same place the matching rule lives — in the book — rather than as a branch inside the matcher. That is what keeps the fairness claim auditable: a trader can reason about a rule encoded in the data structure, and cannot reason about an accumulated pile of exceptions to it.

  • pricethen time
  • FIFOper level
  • deterministicfills

Back of the envelope

sort by price, then time (FIFO)
the matching rule is the data structure
price levels = FIFO queues
best bid / ask sit at the top
deterministic, auditable fills
every trader knows their place in line

Step 3 · Guard the book

Pre-trade risk checks

A trader could submit an order they can’t afford, or one that blows past position limits — a “fat finger” or a rogue algo. Letting that reach the book risks trades that can’t settle.

Order Gatewayvalidate + admitMatching Engineprice-timeRisk Checklimits · funds
New in this step: Risk Check.

A trader could submit an order they can’t afford or that blows past limits (fat-finger, rogue algo). Where do you stop it?

  1. Unwinding executed trades is disruptive, may be impossible once others traded against them, and corrupts the market. Bad orders must be stopped before they execute, not after.

  2. Putting credit/position checks in the single-threaded hot loop adds latency and non-determinism to the core. Keep the matcher tiny and fast; validate before it.

  3. Verify buying power, position/credit limits and price/size sanity at the gateway, rejecting violations immediately — so only valid orders ever touch the engine, and the hot loop stays fast and deterministic.

Insert a Risk Check before matching: verify buying power, position and credit limits, and sanity-check price/size. Reject violations immediately, so only valid orders ever touch the matching engine.

Why this piece earns its place

A risk check that reads position and buying power has a lag the diagram doesn’t show: the numbers it reads are updated by fills, and fills come back from the engine after admission. A trader firing orders faster than that fill stream returns can send a run of orders that are each individually inside the limit and collectively far outside it — which is precisely the shape of a runaway algorithm, so the check fails on the traffic it exists to stop. The fix is to stop treating admission as a read. Admission reserves: buying power is committed the moment the order is accepted, and released on cancel, on reject, and on whatever part of the order never traded. Now the limit holds no matter how far behind the fill stream runs, because the money and the order are committed in the same instant. What that costs is real and worth saying out loud: a trader’s usable capital is tied up by resting orders they may never trade, so quoting on both sides of a wide book consumes limit they are not really using. That complaint is the right one to accept here.

What the new pieces do

Risk Checkguard
Pre-trade gate: does the trader have the buying power and stay within position/credit limits? Rejects bad orders before they reach the book.

Step 4 · Make it deterministic

A single sequencer

Orders arrive concurrently from everywhere. For fairness, auditability, and the ability to run hot replicas, every component must agree on the exact order events were processed — and racing threads can’t guarantee that.

Order Gatewayvalidate + admitSequencertotal orderMatching Enginesingle-threaded
New in this step: Sequencer.

Orders arrive concurrently from everywhere. How do you make matching fair, auditable, and replica-safe?

  1. Racing threads can’t guarantee the order events were processed, breaking fairness and making the engine non-reproducible — so replicas diverge. The core must process one agreed order.

  2. A sequencer stamps every admitted order with a global sequence number, then a single thread processes that one stream — same input order, same output, on primary and every replica. Astonishingly fast in-memory, and perfectly reproducible.

  3. Wall clocks differ across machines and tie at microsecond scale, so timestamps don’t give a single agreed order. You need one authoritative sequencer assigning a total order, not independent clocks.

Funnel all admitted orders through one Sequencer that assigns a global sequence number, then feed that single stream to a single-threaded matching engine. Same input order, same output, every time — on the primary and every replica.

Why this piece earns its place

Ordering the input is only half of determinism. The other half is a discipline inside the engine: it must never read anything that is not in the sequenced stream. A wall-clock read stamped onto a trade, a random tiebreak, an iteration over a hash map whose order depends on where things landed in memory, a background thread expiring day orders on its own schedule — every one of those is a place where the primary and a replica, fed identical input, quietly produce different books. Nothing reports it, and the disagreement surfaces at failover, over someone’s fill. So anything time-driven enters as an event through the sequencer and is processed at its sequence position like everything else; the engine asks the world nothing. The sequencer then deserves all the paranoia the matcher no longer needs, because it is the one component whose failure stops the market and whose own failover can corrupt it. A gap in the numbering is recoverable — consumers can detect and wait. A reissued number is not: two different events wear the same position, and every replica that trusts the stream diverges from every other.

  • 1sequencer
  • single-threadmatcher
  • µsper match

What the new pieces do

Sequencerservice
Stamps every order with a single, global sequence number so all replicas process events in the exact same order — the basis of determinism and fairness.

Back of the envelope

1 sequencer ⇒ total order
every replica sees the same input stream
single-threaded, in-memory
no shared-state races, µs per match
same input ⇒ same output
perfectly reproducible for failover

Step 5 · Tell the world

Market data feed

Every participant needs to see book updates and trades to make decisions — and they must all see them at the same time. Any latency advantage is an unfair edge worth millions.

SequencerMatching EngineOrder BookMarket DataMarket Feed
New in this step: Market Data, Market Feed. · swipe to pan the diagram

Every participant needs book updates and trades — and a latency edge is worth millions. How do you distribute fairly?

  1. Polling gives whoever polls fastest (or is closest) a systematic edge, and hammers the engine. Market data must be pushed equally, not pulled at each participant’s own rate.

  2. Selling a latency advantage is exactly the unfairness a fair exchange must prevent — it lets some systematically see the market first. Delivery should be equal-latency for all.

  3. The engine emits each change to a market feed (often multicast) so the same picture reaches everyone simultaneously. Distributing data fairly matters as much as matching fairly.

The engine emits every change to Market Data, broadcast over a Market Feed to all participants with equal-latency delivery (often multicast). The same picture reaches everyone simultaneously.

Why this piece earns its place

Broadcast buys fairness by giving up the thing point-to-point delivery hands you for free: the feed cannot slow down for anybody. If one subscriber falls behind and the publisher waits for it, that subscriber has just applied backpressure to the entire market — a worse unfairness than the one being solved. So the feed is deliberately fire-and-forget, and every message carries its own sequence number so a receiver can tell that it missed one rather than silently holding a corrupted book. Recovery then has to live off the broadcast path entirely: a separate retransmit-request channel with its own capacity, and a periodic full snapshot of the book so a client that starts late or gaps badly can resynchronise without ever asking the matching engine for anything. Running two identical feeds over independent network paths, with receivers taking whichever copy arrives first, turns most single-packet losses into a non-event. The snapshot cadence is the knob: too rare and a gapped participant trades blind for a long stretch, too frequent and the snapshots eat the bandwidth the live feed needs at the open, when gaps and volume both peak together.

What the new pieces do

Market Datastore
The stream of book changes and executed trades. Every participant needs the same view of the market at the same time.
Market Feedbus
Publishes market data to all participants simultaneously, with equal-latency delivery so no one gets an unfair early peek.

Step 6 · Never lose a trade

Journal & replay

The order book lives in memory for speed — but memory is volatile. A crash without a record would lose orders and trades, which for an exchange is catastrophic and possibly illegal.

Matching Enginesingle-threadedOrder Bookprice levelsJournalappend-only log
New in this step: Journal.

The order book lives in volatile memory for speed. How do you survive a crash without losing trades?

  1. A snapshot every few seconds loses every order and trade since the last one — catastrophic and possibly illegal for an exchange. Every event must be durable before it takes effect.

  2. Log first, apply second: on failure a replica replays the journal to reconstruct the exact book, and because the engine is deterministic the replay reproduces an identical result. The sequential write is cheap.

  3. Synchronously copying full book state on every change is slow and still loses the in-flight event on a crash. Journaling the ordered event stream is cheaper and, with determinism, rebuilds state perfectly anywhere.

Journal every event to an append-only log before applying it. On failure, a replica replays the journal to reconstruct the exact book state and takes over. Because the engine is deterministic, replay reproduces the identical result.

Why this piece earns its place

Log first, apply second settles durability. The ordering that actually bites is subtler: journal before you tell anyone. If a trade is published to the market feed, or acknowledged back to the trader, before that event is safely on the log, a crash in that window leaves the world holding a trade the replica will never replay. The rebuilt book is internally perfect and disagrees with what every participant already saw and traded against — which is strictly worse than losing the event outright, because nothing in the system detects it. So the journal write sits on the critical path ahead of every outbound message, and that makes what counts as written the real decision. A write into the operating system’s page cache is fast and survives the process dying, but not the machine losing power. A synchronous flush to the device survives the machine and charges latency to every event on the hot path. The usual resolution is to redefine durable as on the log on more than one machine, since simultaneous loss of several is far rarer than the loss of any one — which is also what puts the replica in a position to take over at all.

What the new pieces do

Journalstore
A durable, ordered log of every event before it’s applied. Replaying it rebuilds the exact book state — the safety net behind an in-memory engine.

Back of the envelope

append before apply
the sequential journal write is cheap
deterministic ⇒ replay = rebuild
the log is a perfect recipe
replica replays ⇒ takes over
in-memory speed without losing data

Step 7 · After the match

Clearing, settlement & scale

A match is a promise, not finished money. Ownership and cash must actually change hands, and one engine can’t hold the order books for every symbol on earth.

Matching Enginesingle-threadedClearing & Settlementpost-trade
New in this step: Clearing & Settlement.

Hand executed trades to Clearing & Settlement (T+1/T+2) off the hot path, so post-trade work never slows matching. Scale by partitioning symbols across matching engines — each symbol’s book is independent, so AAPL and TSLA run on different engines in parallel.

Why this piece earns its place

Partitioning works because orders for different symbols never interact — and that assumption stops holding in two places worth naming before someone else names them. The first is the product. A spread or multi-leg order wants every leg to fill or none of them to, and if the legs sit on different engines nobody owns that atomicity. The answer isn’t cross-engine coordination, which would hand the hot loop a distributed transaction and cost it the determinism the last few steps bought; it’s to route a related family of products to one engine, and to treat a genuine combination as its own instrument with its own book that publishes into the outright books. The second is risk, and it’s the more awkward one. Buying power is per account, not per symbol, and one account trades across many engines — so the reservation taken on the admission path has to live in a single place that spans the whole partitioned fleet. That place is now in front of every order the exchange accepts. It is the honest scaling limit of this design: matching shards beautifully, and the thing standing in front of matching does not.

What the new pieces do

Clearing & Settlementservice
After a match, transfers ownership and money between parties (T+1/T+2). Decoupled from matching so settlement never slows the hot path.

You did it

You just designed a stock exchange.

TraderOrder GatewaySequencerMatching EngineOrder BookRisk CheckMarket DataJournalClearing & SettlementMarket Feed
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • A matching engine crossing an in-memory order book turns orders into trades.
  • Price-time priority — best price, then FIFO — makes matching fair by construction.
  • Pre-trade risk checks at the edge keep invalid orders out of the core.
  • A single sequencer + single-threaded engine give deterministic, fair, fast matching.
  • A market-data feed broadcasts the same view to all participants with equal latency.
  • An append-only journal plus deterministic replay make the in-memory engine durable.
  • Clearing runs off the hot path; partitioning by symbol scales the exchange out.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Why single-threaded — isn’t parallel matching faster?

    For a single symbol’s book, no: one thread on modern hardware matches millions of orders/sec entirely in L1/L2 cache, with zero locking, context switches, or cross-core coordination — which multi-threading pays dearly for. Parallelism comes from partitioning by symbol (different books on different engines), not from threading one book. Determinism (easy failover) is the bonus.

  2. How does failover work without losing the in-flight order?

    The sequenced stream and every event are journaled before being applied, and replicas consume the same stream. On primary failure, a replica that has replayed up to sequence N takes over from N+1 — the book is bit-identical because the engine is deterministic. The journal is the source of truth; the in-memory book is a derived, rebuildable view.

  3. What matching rules exist beyond price-time (e.g. pro-rata)?

    Price-time (FIFO) is most common, rewarding the earliest order at a price. Some markets (often futures/options) use pro-rata — allocating a fill across resting orders proportional to size — or hybrids. The choice shifts incentives (FIFO rewards speed, pro-rata rewards posting size) but must always be deterministic and encoded in the book.

  4. How do you keep clearing/settlement from slowing matching?

    Decouple it: the engine emits executed trades to clearing asynchronously and moves on. Settlement (T+1/T+2 transfer of ownership and cash via a clearing house that nets exposures) runs entirely off the hot path. A match is a binding promise recorded in the journal; turning it into settled money is a separate, slower pipeline.

  5. How do you handle a flash-crash or runaway algo?

    Layered safeguards around the matching rule: pre-trade risk limits at the gateway, exchange circuit breakers that halt a symbol when price moves beyond a band in a short window, price collars that reject orders far from the last trade, and per-participant kill switches. The matcher stays simple and fast; market-integrity controls live around it.

Check yourself — the answers, and why

Eight steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. Matching is fast because the order book is…

    Matching = crossing the spread; the book is sorted so the best prices are instantly available.

  2. Price-time priority means…

    Price levels as FIFO queues bake fairness into the data structure — deterministic and auditable.

  3. A single sequencer + single-threaded engine give…

    One ordered input stream, no races — same input always yields the same output, on every replica.

  4. Pre-trade risk checks live on the admission path so that…

    Validate at the edge; keep credit/position checks out of the single-threaded core.

  5. The in-memory engine survives crashes via…

    Log first, apply second; determinism makes replay rebuild the exact book anywhere.

Worked example: BUY 100 AAPL @ $50 against a resting book

A trader sends the page's own order, a limit BUY 100 AAPL @ $50.00. The ask side of the book holds $49.98 × 30, then at $50.00 two queued orders: 50 (older) and 40 (newer), then $50.02 × 200. The book contents are illustrative, and the arithmetic is exact.

StepWhereWhat happensNumbers
1Trader → Order GatewayAuthenticates the trader and validates the symbol, side, size and price.1 order
2Risk CheckReserves buying power at admission instead of just reading it, so a burst of orders cannot outrun the fill stream.100 × $50.00 = $5,000 reserved
3SequencerStamps one global sequence number. Every replica will process this order at exactly that position.1 seq no.
4JournalAppends the order event before the engine applies it.1 append, durable on > 1 machine
5Matching Engine + Order BookCrosses the spread at resting prices. It takes $49.98 first, then the $50.00 level in FIFO order: the older 50 in full, then 20 of the newer 40. The newer order keeps its place with 20 left.30 @ $49.98 + 50 @ $50.00 + 20 @ $50.00
6Journal → TraderThe three trades are journaled first, and only then is the trader told.filled 100, avg $49.994
7Market Data → Market FeedBroadcasts the trades and level changes to everyone at once, each message sequence-numbered so a receiver can detect a gap.3 trades + 2 level updates
8Risk CheckReleases the part of the reservation that did not trade.$5,000 − $4,999.40 = $0.60 released
9Clearing & SettlementTakes the three trades off the hot path for transfer of ownership and cash.T+1/T+2

Matching itself (row 5) is microseconds of in-memory pointer work. The journal writes in rows 4 and 6 sit on the critical path ahead of every outbound message, so durability, not matching, sets the per-event latency (Step 6). Price-time priority shows in row 5: the newer $50.00 order waited behind the older one, and its 20 leftover shares keep their place (Step 2). At 10× the order rate, symbols spread across engines (Step 7). The account-wide reservation in rows 2 and 8 does not split by symbol, so it is the first thing to saturate.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Match: take buy/sell orders and cross them into trades against an in-memory order book.
  • Order fairly: price-time priority — best price, then earliest (FIFO) at that price.
  • Guard the book: reject orders a trader can’t afford or that blow past limits, before matching.
  • Broadcast the market: publish book changes and trades to every participant with equal latency.
  • Never lose a trade: journal every event before applying it; replay to rebuild the exact book.

The qualities that shape everything

Each one names the mechanism that buys it.

Match in microseconds
An in-memory Order Book of sorted resting bids/asks lets matching just cross the spread — best bid against best ask — with no disk round trip or scan.
Fair, auditable fills
Price-time priority organizes the book into price levels, each a FIFO queue, so the matching rule is built into the data structure — best price, then earliest.
Invalid orders never touch the core
A pre-trade Risk Check on the admission path verifies buying power and position/credit limits before matching, keeping the hot loop fast and deterministic.
Deterministic and replica-safe
One Sequencer assigns a global sequence number and a single-threaded engine consumes that one stream — same input order, same output, on primary and every replica.
No one sees the market first
The engine broadcasts every change to a Market Feed with equal-latency delivery (often multicast) so the same picture reaches everyone simultaneously.
Survive a crash without losing a trade
Journal every event to an append-only log before applying it; a replica replays it and — because the engine is deterministic — rebuilds a bit-identical book and takes over.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

An in-memory sorted order book over storing orders in a database and querying for matches

A disk round-trip per order is orders of magnitude too slow and SQL can’t express price-time matching cheaply. The book must live in memory, purpose-built, so matching is just crossing the spread.

Price-time priority (FIFO per level) over largest order first

Favoring size lets big players jump the queue and punishes small orders — manifestly unfair, and traders leave. Best price then earliest is deterministic and auditable; fairness can’t depend on order size.

Pre-trade risk on the admission path over checking risk inside the matching engine

Credit/position checks in the single-threaded hot loop add latency and non-determinism to the core. Validate at the edge so only valid orders reach the matcher and the hot loop stays fast.

One sequencer + single-threaded engine over concurrent multi-threaded matching

Racing threads can’t guarantee the order events were processed, breaking fairness and making replicas diverge. One agreed input stream through one thread is reproducible — and astonishingly fast in-memory.

Journal every event before applying it over periodic snapshots of the book to disk

A snapshot every few seconds loses every order and trade since the last one — catastrophic for an exchange. Log first, apply second: the sequential write is cheap and deterministic replay rebuilds the exact book.

The answer, out loud

What a strong answer to “Design a Stock Exchange” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–3 min

    What we’re actually building

    Let me say back what I think this is, because exchange is a broad word. I’m building the venue itself, not a broker: orders arrive, and my job is to turn them into trades, decide who trades first, tell everybody what happened, and be able to prove all of it afterwards. Two things are absolute here and they pull against each other. It has to be fast, because latency is the product on a venue like this, and it has to be correct and fair, because money moves and someone can ask me to reconstruct any moment of the day. If those ever collide I’d rather be a little slower than be unable to explain a fill.

  2. 3–9 min

    The book and the match

    The core is smaller than people expect. I keep an order book per symbol entirely in memory: resting buy orders sorted with the highest price first, sells with the lowest first. An incoming order crosses the spread — best bid against best ask — taking fills until the two sides no longer overlap, and whatever is left rests in the book if it was a limit order. I’m not putting this in a database. A disk round trip per order is hopelessly slow for something I want to answer in microseconds, and matching isn’t really a query anyway; it’s a short walk down a sorted structure I want sitting in cache.

    Built in step 1: Match orders into trades
  3. 9–15 min

    Who trades first

    Then the rule, which is the part traders actually care about. Best price wins, and among orders at the same price, the earliest one fills first. I want that to be the shape of the data rather than a policy the matcher applies on top, so the book is a set of price levels and each level is a first-in-first-out queue. Matching is then just taking from the front. I wouldn’t match largest order first: it lets size jump the queue and tells smaller participants they’re second class, and they will go somewhere else. And I wouldn’t break ties randomly, because then nobody can tell where they stand in line.

    Built in step 2: Price-time priority
  4. 15–21 min

    Stopping bad orders at the door

    Before anything reaches the book I want a risk check on the admission path. Does this account have the money, is it inside its position and credit limits, are the price and size sane. A fat-finger order or a misbehaving algorithm gets rejected right there. I’m deliberately keeping this out of the matching engine: putting credit checks in the hot loop makes the core slower and gives it more state to be wrong about, and I still have to do the work anyway. And I don’t want to match first and unwind afterwards — once other people have traded against that fill, unwinding means tearing up real trades.

    Built in step 3: Pre-trade risk checks
  5. 21–28 min

    Making it deterministic

    Now the part I’d spend the most time on. Orders arrive concurrently from many gateways, and I need one agreed order of events, because fairness is fundamentally a claim about sequence. So everything admitted passes through a single sequencer that stamps a global sequence number, and one thread consumes that stream and does the matching. People hear single-threaded and assume bottleneck. In memory, with no locks and no handoff between cores, it’s extremely fast, and it buys something I can’t get any other way: the same input always gives the same output, so a replica fed the same stream holds the same book. I wouldn’t sort by timestamps instead — clocks disagree between machines and tie at this resolution.

    Built in step 4: A single sequencer
  6. 28–34 min

    Telling everyone at once

    Everyone needs to see book changes and trades, and they need to see them at the same instant, because a systematic head start is worth an enormous amount and it’s exactly what makes people distrust a venue. So the engine emits every change once and it goes out as a broadcast, typically multicast, so a single send reaches all participants together. I wouldn’t let participants poll for updates: then whoever polls fastest or sits closest wins, and they’d hammer the engine while doing it. And I obviously wouldn’t sell a faster copy to whoever pays the most, since that’s the precise unfairness I’m being paid to prevent.

    Built in step 5: Market data feed
  7. 34–40 min

    Surviving a crash

    The book lives in volatile memory, so I need an answer for a crash that isn’t we lost the morning. Every event is appended to a journal before it’s applied. Because the engine is deterministic, that log is a complete recipe: a replica replays it, ends up with an identical book down to the queue positions, and can take over from the next sequence number. I’d take that over snapshotting the book every few seconds, which quietly accepts losing every order and trade since the last snapshot. For an exchange that isn’t a performance trade-off, it’s a legal problem.

    Built in step 6: Journal & replay
  8. 40–45 min

    After the match, and what I’d do next

    Once a trade happens I hand it to clearing and settlement and get out of the way — ownership and cash move on a T+1 or T+2 timescale and none of that belongs anywhere near matching. To scale I partition symbols across engines, since two symbols never touch each other’s books. The trade-off running through the whole design is reach against simplicity: I keep the core deliberately tiny and single-threaded, and pay for that by pushing risk, distribution, durability and settlement outward around it. With more time I’d build out market-integrity controls — circuit breakers and price bands for a symbol that runs away — and the operational side of failover: how a replica gets promoted, and how gateways learn where to send the next order without two engines both believing they own a symbol.

    Built in step 7: Clearing, settlement & scale

What this teaches

Learn system design by building a stock exchange / matching engine step by step. An interactive guide covering the order book and price-time priority, the matching engine, pre-trade risk checks, deterministic sequencing for low latency, market data feeds, journaling and replay, and clearing.

Key takeaways

  • A matching engine crossing an in-memory order book turns orders into trades.
  • Price-time priority — best price, then FIFO — makes matching fair by construction.
  • Pre-trade risk checks at the edge keep invalid orders out of the core.
  • A single sequencer + single-threaded engine give deterministic, fair, fast matching.
  • A market-data feed broadcasts the same view to all participants with equal latency.
  • An append-only journal plus deterministic replay make the in-memory engine durable.
  • Clearing runs off the hot path; partitioning by symbol scales the exchange out.

Concepts covered

  • What is a stock exchange?
  • Match orders into trades
  • Price-time priority
  • Pre-trade risk checks
  • A single sequencer
  • Market data feed
  • Journal & replay
  • Clearing, settlement & scale
built to be matched, not memorized — make the calls, crash the engine, run the gauntlet.
Finished this one? 0 / 65 System Designs done
NextDesign a Rate Limiter Closest system design to this one

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More System Designs