System design interview guide
Design an Order Matching Engine: Order Book, Price-Time Priority and Order Types
Short answer
An order matching engine keeps an order book for each instrument: buy orders sorted from the highest price down, sell orders from the lowest price up, and at each price a queue of orders in arrival order. When a new order arrives, it trades against the best opposite price first, and at that price against the oldest order first (price-time priority), at the resting order's price. What is left either rests in the book or is cancelled, depending on its time in force (GTC, IOC or FOK). Each book is run by one thread in memory, fed by a sequencer that writes every input to a journal first, so the same input always gives the same trades and a standby can replay it to an identical book.
Designing the whole exchange around the engine? See the stock exchange system design guide
Two courses by the author of this page:
770 lessons · 18 free to read
₹1,777 in India · $49 elsewhere, once
Get System DesignYou own this course
210 lessons · 10 free to read
₹2,777 in India · $49 elsewhere, once
Get AI EngineeringYou own this course
Save your progress and get the interview checklist PDF
Every exchange, from a stock market to a crypto app, has one small program at its heart: the matching engine. It holds every buy and sell order that is still waiting, and when a new order arrives it decides, in a few microseconds, who trades with whom, how much, and at what price. Take an interview-sized venue with 300 million order messages a day. That is about 12,800 messages a second on average and about 256,000 a second in the busiest second of the open. If one thread handles them all, it has under 4 microseconds per message. It must also give the same answer every time for the same input, never lose an order it said yes to, and treat every trader by the same rule. Those four demands shape every choice in this design.
An order matching engine pairs buy orders with sell orders. First, some terms. An order says: buy or sell, how many (the quantity), and usually a price limit. A limit order says the worst price you accept, for example 'buy 100 at 101 or less'. A market order takes the best prices available now. The order book is the list of limit orders that are still waiting, called resting orders. Buy orders are bids and sell orders are asks. Each side is grouped into price levels, and each level is a queue of orders at that price in the order they arrived. The bid side is sorted from the highest price down, and the ask side from the lowest price up. The best bid and best ask are the top of the book. When a new order arrives, the engine checks if it crosses: a buy crosses when its limit is at or above the best ask. If so, it trades against the best price first, and at each price against the oldest order first. This rule is called price-time priority, or FIFO (first in, first out). The trade happens at the resting order's price. Whatever is left of a limit order then rests in the book; what is left of a market or IOC order is dropped. Time in force rules say how long an order lives: GTC (good till cancelled), IOC (immediate or cancel: fill what you can now, drop the rest) and FOK (fill or kill: fill all of it now or nothing). The engine also stops a trader from trading with themselves (self-trade prevention). Some futures markets split a fill across the orders at a price in proportion to their size instead (pro rata). To be fast and fair, each book is run by a single thread in memory, fed by one ordered stream of input that is written to a journal first. Same input in the same order gives the same trades, so a replica can replay the journal and hold an identical book.
Where it shows up
Asked as 'design a matching engine', 'design an order book', 'design a stock exchange' or 'design a crypto exchange', and as a low level design (LLD) coding round where you write the order book class in an hour. Trading firms, exchanges, crypto companies and fintech teams ask it in depth; general tech companies use it as a hard data structures and concurrency question.
Spec sheetan order matching engine
- 01Message rate
- about 12,800 a second on average, 256,000 at the open
- 02Time per message on one thread
- under 4 microseconds at the open
- 03The busiest instrument
- about 12,800 messages a second
- 04Journal size
- about 30 GB a day, 26 MB a second at peak
worked through below, with the maths
Why this question is asked
It tests whether you can pick data structures from the operations you need, not from habit. A weak answer puts orders in a database table and sorts on every request. A strong answer lists the operations first (find the best price, add an order at a price, cancel an order by id, fill from the front of a queue) and then picks a structure that makes each one fast. It also tests how you think about fairness and correctness: why one thread is better than many here, what 'the same input gives the same output' buys you, and how a journal lets you recover without losing an order. Finally it tests whether you know the rules traders care about: time in force, market orders with nothing to trade against, self-trade prevention, and FIFO vs pro rata.
This page is the free part.
The course goes deeper on the order matching engine design
₹1,777 in India$49 everywhere elseonce, for the whole course
The System Design course covers the order matching engine design across a run of lessons, not one page. These 4 alone are about 88 minutes of step-by-step reading, 2 of them with code you run in the browser, all with a quiz.
- Event Sourcing
Store events instead of state, rebuild any point in time from the log of what happened.
- State Machine
Model system behavior as a finite set of states and transitions, the foundation of distributed protocols.
- Write-Ahead Logging
Write changes to a sequential log before applying them, the foundation of database crash recovery.
- Snapshotting
How systems take a consistent snapshot without stopping writes. Compares four techniques, including Redis fork with copy-on-write and Postgres MVCC.
all part of
System Design Masterclass
770 lessons · about 283 hours · 18 free to read
₹1,777in India, by UPI
$49everywhere else, by PayPal
It is really nice and good Please join the course
You own this course
Continue with Event Sourcingpay once,
yours for life
Live from the course bench
Before you design Order Matching Engine, try the questions it rests on.
Real interview questions, live diagrams and measured lab results, pulled at random from the lessons. A new set every time you come back.
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 new limit and market orders, cancels, and quantity changes for one instrument (a stock, a future or a coin pair)
- Match by price-time priority: best price first, then the oldest order at that price
- Support time in force: GTC, Day, IOC (immediate or cancel) and FOK (fill or kill), plus post-only orders that must never take
- Prevent self-trades: two orders from one owner must never trade with each other
- Report every change: an acknowledgement for each order, a fill report to both sides of each trade, and book updates for market data
- Optionally allocate by pro rata instead of FIFO, for markets whose rules ask for it
Non-functional requirements
- Low and steady latency: a few microseconds per message inside the engine, with no long pauses at the worst moment
- Determinism: the same ordered input always produces the same trades and the same book
- Durability: an order the engine acknowledged is never lost, even if the machine dies a moment later
- Fairness: one written rule decides priority, and arrival order is set in one place, not by which thread ran first
- Fast recovery: a standby can take over, and a restarted engine can rebuild its book from the journal in seconds
- Scale out by instrument: more instruments means more engines, not a bigger shared lock
Back-of-envelope scale estimates
Show your math. Pulling numbers from thin air signals you have not thought about the load.
Message rate
about 12,800 a second on average, 256,000 at the open
Interview assumptions: 300 million messages a day (new orders, cancels and changes) over a 6.5-hour session of 23,400 seconds. 300,000,000 / 23,400 = 12,821 a second on average. Assume the busiest second at the open is 20 times the average: 256,410 a second. Say these assumptions out loud first. Most messages are cancels and changes, not trades.
Time per message on one thread
under 4 microseconds at the open
If one thread handled every instrument at the open: 1 second / 256,410 = 3.9 microseconds per message. That leaves no room for a database call, a lock wait or a long garbage collection pause. For scale, LMAX reported 6 million orders a second on one thread for its business logic, which is 0.17 microseconds each. Matching is a different workload, but it shows what one thread with no locks can do.
The busiest instrument
about 12,800 messages a second
Interview assumption: the busiest instrument gets 5% of all messages. 256,410 x 0.05 = 12,821 a second at the open, so 78 microseconds per message. Engines are split by instrument, so the hottest one sets the size of one engine thread, and the total sets how many threads you run.
Journal size
about 30 GB a day, 26 MB a second at peak
Every input is written to an append-only journal before it is processed. Interview assumption: 100 bytes per record. 300,000,000 x 100 bytes = 30 GB a day. At the open: 256,410 x 100 bytes = 26 MB a second. Writing to the end of a file is the fastest thing a disk does, so this is easy.
Memory for every book
about 128 MB
Interview assumptions: 2 million orders resting across the venue at the busiest moment, 64 bytes each (id, owner, quantity, price index and two links). 2,000,000 x 64 = 128 MB. With 5,000 instruments that is 400 orders, about 26 KB, per book on average. The whole venue fits in the memory of one server, which is why the book lives in memory and not in a database.
Price levels as an array
2,001 levels per side, about 16 KB
Interview assumptions: a price band of 10% either side of 100.00 and a tick size (the smallest price step) of 0.01. 90.00 to 110.00 in steps of 0.01 is 2,001 levels per side. One 8-byte pointer per level is 16 KB, small enough to stay in fast CPU cache. Finding the level for 100.37 is one subtraction and one division: (100.37 - 90.00) / 0.01 = index 1037.
Replaying a day
about 30 seconds of reading
Interview assumption: the journal disk reads 1 GB a second in order. 30 GB / 1 GB a second = 30 seconds to read a full day, before processing. Taking a snapshot of the book every so often cuts this further. LMAX reported that a full restart, including a day's journals, took under a minute.
High-level architecture
Draw a line of boxes from left to right. On the left are gateways. A gateway accepts connections from traders, checks who they are, decodes each message (often FIX, the standard messaging format of trading, or a binary format), and runs cheap checks such as a valid quantity and a price inside the allowed band. Next is the risk check: does this account have the money or the position for this order? Then comes the sequencer, the most important box. It puts every message for an instrument into one single line and gives each a sequence number that only grows. It writes each message to an append-only journal and sends it to one or more standby machines before the engine acts on it. Arrival order is decided here, once, and that is what makes the system fair and repeatable. Next is the matching engine itself: one thread per instrument (or per group of instruments) that reads the sequenced messages one by one and applies each to that instrument's order book in memory. It never waits for a disk, a network call or a lock. For each message it produces output events: accepted, rejected, filled, partly filled, cancelled, and book changed. On the right, those events go out on two paths. Execution reports go back through the gateways to the traders whose orders traded. Market data events become the public feed: the top of the book, the depth at each price, and the list of trades. The standby engines read the same sequenced journal and run the same code, so they hold the same book and can take over at once. This guide is about the engine and its book; the stock exchange guide covers the whole venue around it.
In a real interview, sketch this on the whiteboard before diving into any single box.
One buy order, from arrival to trades
The book has asks of 50 at 100 (Ben, first) and 70 at 100 (Cy, second), and 100 at 101 (Ana). Dev sends: buy 200 at 101, fill or kill. This is a good order to explain it in an interview.
- 1
Checked
The gateway checks Dev's session, the quantity and that 101 is inside the price band. The risk check confirms Dev can pay for 200 at up to 101.
- 2
Sequenced
The sequencer gives the order the next sequence number, writes it to the journal and sends it to the standby. Only then does the engine see it.
- 3
Fill or kill check
FOK means all or nothing, so the engine first adds up the asks at 101 or lower: 50 + 70 + 100 = 220. That covers 200, so it goes ahead. With only 150 available, it would reject the order and change nothing.
- 4
Best price first
The best ask is 100. Within 100, Ben came first, so Dev buys 50 from Ben at 100, then 70 from Cy at 100. The 100 level is now empty.
- 5
Next price
The next best ask is 101. Dev buys the remaining 80 from Ana at 101. Ana's order stays in the book with 20 left.
- 6
Reported
The engine emits three fills: 50 at 100, 70 at 100, 80 at 101. Each fill goes to both sides as an execution report. Market data shows two trades at 100, one at 101, the 100 level gone and 20 left at 101.
- 7
Same everywhere
The standby read the same journal entry and ran the same steps, so its book is identical, down to Ana's 20.
Core components
Walk through each service. The interviewer wants to hear what each one owns, not just the names.
Gateways
Hold trader connections, check identity, decode messages (FIX or binary), and run cheap stateless checks such as quantity and price band. They do no matching, so you can run as many as you need.
Pre-trade risk check
Before an order reaches the book, confirms the account can afford it or holds what it sells, and applies limits such as maximum order size. It must be fast, because it sits in front of every order.
Sequencer and journal
Puts every message for an instrument into one line with a growing sequence number, writes it to an append-only journal and copies it to standbys before the engine acts. This single point of ordering is what makes matching fair and repeatable.
Matching engine thread
One thread per instrument or per group of instruments. Applies each sequenced message to the order book in memory, in order, and emits output events. No locks, no disk, no network calls on this path.
Order book
For each side: price levels sorted best first, each level a first-in-first-out queue of orders, plus a map from order id to its place so a cancel is found at once. Lives entirely in memory.
Output publisher
Turns engine events into execution reports for the traders involved and a market data feed for everyone: top of book, depth by price and the trade list, usually as a snapshot plus small updates.
Standby engines
Read the same journal and run the same code, so they hold the same book. If the live engine fails, one takes over from the last sequence number. LMAX runs several processors that each handle every input event and uses only the live one's output.
Data model
Pick the right store per table. Justify each choice with the access pattern, not by reflex.
order (in memory)order_idowner_idside: buy or sellprice (as an integer number of ticks)remaining_qtytime_in_forcestp_modeprev, next (links in its price queue)Prices are stored as whole numbers of ticks, never as floating point, so 100.37 with a tick of 0.01 is 10037 and comparisons are exact. The two links let the engine remove an order from the middle of its queue in constant time when it is cancelled.
price_level (in memory)pricehead, tail (first and last order)total_qtyorder_countOne per price that has orders. total_qty makes depth questions such as the fill or kill check fast, without walking every order.
book side (in memory)levels indexed by pricebest_priceEither an array indexed by price tick (fast when prices sit in a known band), or a sorted structure such as a balanced tree or skip list (when the range is wide). best_price is kept up to date so reading the top of the book is one lookup.
order_index (in memory)order_idpointer to the order nodeA hash map, so a cancel or a change finds its order in constant time instead of scanning a price level. Cancels are most of the traffic, so this matters as much as matching does.
journal (on disk)sequence_numberinstrument_idmessage type and fieldsgateway receive timeAppend-only, written and copied to standbys before processing. Replaying it from the start, or from the last snapshot, rebuilds the book exactly.
snapshot (on disk)instrument_idlast sequence_number includedevery resting order in priority orderTaken from a standby or at a quiet moment, so the live engine never pauses. Recovery loads the snapshot, then replays the journal from the next sequence number.
Deep dives
These are the conversations the interviewer is steering you toward. Practice each one until you can talk through it without notes.
The order book: picking the data structure from the operations
List the operations before you pick a structure. The engine must, very often: read the best price on each side; add an order at a price, behind the orders already there; take quantity from the front order at the best price; and cancel an order by its id, from anywhere in the book. Cancels and changes are usually most of the traffic, so the cancel path matters as much as matching. Now build the book from three parts. First, each price level is a first-in-first-out queue. A doubly linked list works well: adding to the back, taking from the front, and removing from the middle are all constant time if you hold a pointer to the node. Second, a map from order id to that node, so a cancel finds its order at once, with no scanning. Third, a way to find the best price and the next price. There are two common choices. If prices stay inside a known band, use an array indexed by price tick. In our example, a band of 90.00 to 110.00 with a tick of 0.01 is 2,001 levels per side, 16 KB of pointers, and the level for 100.37 is index (100.37 - 90.00) / 0.01 = 1037. Keep the index of the best level, and when that level empties, step to the next non-empty one. If prices can be anywhere, as with many crypto pairs, use a sorted map such as a balanced tree or a skip list, which finds the best price and the next one in logarithmic time. Two rules apply to both. Store prices as whole numbers of ticks, never floating point, so two equal prices always compare equal. And keep total quantity per level, so depth questions do not walk every order. The listing below is a small but complete book in Python. It uses a heap of prices instead of an array, to stay short, and it shows price-time priority, fill or kill, immediate or cancel, self-trade prevention and cancel by id.
from collections import deque
import heapq, itertools
class Book:
"""One instrument's limit order book: price-time priority, one thread."""
def __init__(self):
self.levels = {"buy": {}, "sell": {}} # price -> deque of [id, owner, qty]
self.prices = {"buy": [], "sell": []} # heaps of prices (buy stored negative)
self.where = {} # order id -> (side, price), for O(1) cancel
self.seq = itertools.count(1)
def best(self, side):
heap = self.prices[side]
while heap: # drop prices whose queue is empty
p = -heap[0] if side == "buy" else heap[0]
if self.levels[side].get(p):
return p
heapq.heappop(heap)
self.levels[side].pop(p, None)
return None
def submit(self, owner, side, qty, price=None, tif="GTC"):
oid, other = next(self.seq), ("sell" if side == "buy" else "buy")
crosses = lambda p: price is None or (p <= price if side == "buy" else p >= price)
if tif == "FOK": # fill or kill: check depth first, change nothing
avail = sum(o[2] for p, q in self.levels[other].items() if q and crosses(p) for o in q)
if avail < qty:
return oid, [], "killed"
fills = []
while qty and (p := self.best(other)) is not None and crosses(p):
queue = self.levels[other][p]
maker = queue[0]
if maker[1] == owner: # self-trade prevention: cancel the newer order
return oid, fills, "cancelled (self-trade)"
n = min(qty, maker[2])
fills.append((maker[0], oid, p, n)) # trades at the resting order's price
maker[2] -= n
qty -= n
if maker[2] == 0:
queue.popleft()
del self.where[maker[0]]
if qty and price is not None and tif == "GTC": # the rest waits in the book
queue = self.levels[side].setdefault(price, deque())
if not queue:
heapq.heappush(self.prices[side], -price if side == "buy" else price)
queue.append([oid, owner, qty])
self.where[oid] = (side, price)
return oid, fills, f"resting {qty}"
return oid, fills, "done" if not qty else f"{qty} unfilled, dropped"
def cancel(self, oid):
side, price = self.where.pop(oid)
queue = self.levels[side][price]
queue.remove(next(o for o in queue if o[0] == oid))
book = Book()
print(book.submit("ana", "sell", 100, price=101))
print(book.submit("ben", "sell", 50, price=100))
print(book.submit("cy", "sell", 70, price=100))
print(book.submit("dev", "buy", 200, price=101, tif="FOK"))
print(book.submit("eve", "buy", 90, price=100))
print(book.submit("ana", "buy", 10, price=102))
print(book.submit("fay", "buy", 500, tif="IOC"))
book.cancel(5)
print(book.best("buy"), book.best("sell"))
The matching loop and price-time priority
The loop is short, and an interviewer will ask you to say it precisely. When an order arrives, look at the best price on the other side. A buy crosses if its limit is at or above the best ask; a sell crosses if its limit is at or below the best bid; a market order always crosses while there is anything to trade with. While the order crosses and still has quantity: take the first order in the queue at that best price, trade the smaller of the two quantities, reduce both, remove the resting order if it is now empty, and move to the next price when a level empties. When the loop stops, decide what happens to the rest: a GTC limit order rests in the book at its own price, at the back of that level's queue; anything else is dropped. Two details are easy to get wrong. First, the price of a trade is the resting order's price, not the incoming order's limit. Coinbase's docs state it directly: orders are matched 'at the price of the order on the book'. So a buy limit at 101 that meets an ask at 100 trades at 100, and the buyer gets the better price. Second, 'time' in price-time means the order in which the engine received the order, which is the sequence number, not a clock on the trader's machine. Coinbase's docs say orders are executed in price-time priority 'as received by the matching engine'. Also decide what a change does to priority. A common rule: lowering the quantity keeps the order's place in the queue, while raising the quantity or changing the price is treated as a cancel plus a new order, which goes to the back. Otherwise a trader could join a queue with 1 lot and later grow it to 10,000 while keeping the front spot.
Order types and time in force
Time in force says how long an order may live. The FIX standard, the messaging format most trading systems use, lists the values in tag 59: 0 is Day, 1 is Good Till Cancel (GTC), 3 is Immediate or Cancel (IOC), 4 is Fill or Kill (FOK), and an order with no value is treated as Day. In the engine they are a few lines each. A GTC order rests until it fills or is cancelled. A Day order rests until the session ends, when the engine cancels all remaining Day orders. An IOC order runs the matching loop once and drops whatever is left; it never rests. A FOK order must fill completely or not at all, so the engine checks the available quantity first, at prices the order accepts, and rejects the whole order if it is short, without touching the book. This check is why the book keeps total quantity per price level. A market order has no price limit. It is in effect an IOC with no limit, and a dangerous one: in a thin book it can walk many price levels and fill at a terrible price. Many venues protect against this with a price band or by turning market orders into limit orders a few ticks away. A post-only order is the opposite of a market order: it must never take liquidity. If it would cross on arrival, the engine rejects it (Coinbase's docs say post-only orders are rejected if any part would execute immediately). Market makers use it to make sure they only ever rest in the book. A stop order is held outside the book until the last trade price reaches its trigger, and then enters as a market or limit order. Keep stops in their own sorted list and check it after each trade, inside the same thread, so triggering stays in sequence order too.
Self-trade prevention
A self-trade happens when two orders from the same owner meet: one firm buys from itself. No money really changes hands, but the trade shows up in the public trade list and volume, which can mislead other traders, and regulators treat repeated self-trades as a warning sign. So most venues let the owner choose what the engine does when it is about to happen. Coinbase's docs list four modes, and the incoming (newer) order's mode decides. Decrement and cancel (dc, the default) cancels the smaller order and reduces the larger one by that size, and if they are equal, cancels both. Cancel oldest (co) cancels the resting order and lets the new order keep matching. Cancel newest (cn) cancels the incoming order and leaves the resting one in the book. Cancel both (cb) cancels both at once. In the engine this is one check inside the matching loop, before a trade is made: if the resting order's owner equals the incoming order's owner, apply the mode instead of trading. The owner can be an account, or a group of accounts that one firm marks as one owner. The listing above uses cancel newest. Notice that the check must sit inside the single matching thread. If it ran in a gateway, the gateway would have to read the book, and the answer would be out of date by the time the order arrived.
FIFO vs pro rata allocation
Price-time priority is not the only fair rule. Some futures markets split a fill among all the orders at a price in proportion to their size. This is pro rata. CME Group's documentation for its Globex platform describes both. Under FIFO, quantity goes to resting orders in arrival order. Under pro rata, each order's share is its size divided by the total at that price, times the incoming quantity. Take one price level with A 100, B 300 and C 600 lots, in that order, and an incoming order for 250. FIFO gives A 100 and B 150, and C nothing. Pro rata gives A 250 x 100/1,000 = 25, B 75 and C 150. Shares rarely come out whole. CME's rule is that the quantity to be allocated is rounded down, and any remainders are added on a FIFO basis. For an incoming 333: the exact shares are 33.3, 99.9 and 199.8, rounded down to 33, 99 and 199. That is 331, so 2 lots are left, and they go by FIFO: A, the oldest, gets them and ends with 35. CME also notes that a pro rata step is always followed by a FIFO step. Many real algorithms are mixes: CME's Allocation algorithm first gives priority to a top order (the order that improved the best price), then splits by pro rata, then fills the rest by FIFO. The trade-off is in what each rule rewards. FIFO rewards being first, so traders compete on speed to reach the front of the queue. Pro rata rewards size, so traders may post larger orders than they really want, expecting only a share. For the engine, pro rata means walking the whole level for each fill instead of just the front, so keep total quantity per level and make the allocation a separate, well-tested function.

One thread per book, a journal, and determinism
Why not let many threads match one book in parallel? Because matching one book is a sequence of decisions that each depend on the one before: whether the next order trades depends on what the last order did. Parallel threads would need locks around the book, and then they would mostly wait for each other, and which thread got the lock first would decide priority, which is neither fair nor repeatable. One thread that owns the book needs no locks, keeps its data warm in the CPU cache, and processes messages in exactly the order the sequencer set. This is the LMAX design. Martin Fowler's article describes its Business Logic Processor handling 6 million orders a second on a single thread on a 2010-era server, with no database in the processing path. You still use many threads, just not on one book: split by instrument, so each engine thread owns a set of books, and add threads as instruments grow. The second idea is determinism: same input, same order, same output. If the engine never reads a clock, never uses random numbers and never depends on thread timing, then its output is a pure result of the sequenced input. That gives you two things almost for free. Durability: write each input to the journal before processing it, and an acknowledged order can never be lost, because the book can always be rebuilt by replaying the journal. LMAX journals every input and replicates it to other nodes before processing. Fast failover: standby engines read the same journal and run the same code, so they hold identical books. LMAX runs several processors that each handle every input event, uses only the live one's output, and can switch to another in microseconds. Recovery after a full restart loads the last snapshot and replays the journal after it. With our numbers, a full day's journal is 30 GB, about 30 seconds to read at 1 GB a second; LMAX reported a full restart, including a day's journals, in under a minute. One warning: determinism is easy to break by accident. A hash map whose iteration order changes between runs, a timestamp read inside the engine, or a floating point price can make two replicas disagree. Put timestamps on messages in the sequencer, not in the engine.
Keeping latency low and steady
The average latency matters less than the worst moments, because the worst moments are the open, the close and big news, when everyone sends at once. With our numbers, one thread at the open has under 4 microseconds per message. The rules follow from that. Do nothing slow on the matching path: no disk writes (the sequencer journals before the engine runs), no network calls, no database queries, no locks. Allocate memory up front: create pools of order nodes when the engine starts and reuse them, because asking the operating system for memory, or a garbage collector pausing to free it, can take far longer than a match. Keep data small and close together, so it stays in CPU cache: prices as integers, an array of levels, compact order nodes. Give the engine thread its own CPU core and let it spin while waiting for the next message, instead of sleeping and waking up, which costs time. Pass messages between threads through a pre-allocated ring buffer instead of a lock-protected queue; LMAX's Disruptor is the well-known example. Finally, measure the slowest 1 in 1,000 and 1 in 10,000 messages, not the average, and test with a replay of a real busy open, because that is when the design is judged.

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.
One thread per book vs parallel matching
One thread needs no locks, stays in CPU cache and gives the same output for the same input, which makes fairness, replay and failover simple. Parallel matching on one book needs locks, lets thread timing decide priority and is hard to replay. Scale by giving each thread its own instruments instead.
Array of price levels vs sorted tree or skip list
An array indexed by tick is fastest and fits in cache when prices stay in a band, 2,001 levels and 16 KB in our example. A sorted tree or skip list handles any price range in logarithmic time and less wasted space. Many crypto pairs, whose prices can move by large factors, need the second.
FIFO vs pro rata
FIFO rewards being first and needs only the front of a queue. Pro rata rewards size, makes traders post bigger orders, and needs a walk of the whole price level plus a rounding rule. The market's rulebook decides; the engine should support both behind one tested function.
Journal before matching vs after
Writing and copying each input before the engine acts means an acknowledged order is never lost and any replica can replay to the same book. It adds the journal write to every order's path, so the journal must be an append-only file on fast storage, not a database.
Market orders as written vs protected
A pure market order fills against whatever is there, which in a thin book can mean very bad prices. A price band or converting market orders to limit orders a few ticks away protects traders, at the cost of some market orders not filling fully.
Free PDF · 18 pages
Get the free System Design Interview Cheat Sheet
The interview in seven stages, the numbers worth knowing by heart, and twelve classic systems on one page each, every line linked to the lesson it comes from.
Follow-up questions to expect
After the main design, the interviewer usually picks one part and asks more. Here is what to say.
A trader complains that their order at 100 was in the book first, but someone else's order at 100 filled before it. What do you check?
Check the sequence numbers, not the traders' clocks. Priority is the order in which the engine received the orders. Then check whether the complaining trader changed their order: raising the quantity or changing the price sends it to the back of the queue.
Your replica's book does not match the live engine's book after replay. Where do you look?
For anything that is not a pure result of the input: a clock read inside the engine, random numbers, iteration over a hash map whose order can change, floating point prices, or a code version that differs between the two machines.
A market order to buy 10,000 arrives when only 300 are offered within 1% of the last price. What should happen?
It depends on the venue's protection. With no protection, it walks every level and may fill far from the last price. With a price band, it fills what it can inside the band and the rest is cancelled, like an IOC with a limit.
Cancels are 90% of your traffic. What does that change in the design?
The cancel path must be as fast as matching: a map from order id to the order node, and a doubly linked list per level so removal from the middle is constant time. Also keep total quantity per level so a cancel updates depth without a walk.
How would you add a second region for disaster recovery without breaking fairness?
Keep one sequencer as the single source of order for each instrument. Copy the journal to the second region and run a standby engine there that replays it. Switching regions is a failover, not two engines matching at once, because two sequencers would mean two different orders of arrival.
How an order matching engine actually does it
The pieces in this guide come from public descriptions of real systems. LMAX, a retail financial trading platform, described its architecture through Martin Fowler: a single-threaded Business Logic Processor in memory, input journaled and replicated before processing, nightly snapshots plus replay, a full restart in under a minute, and several processors running every event with only the live one's output used. Coinbase's exchange docs state the rules a crypto matching engine follows: price-time priority as received by the engine, trades at the resting order's price, and four self-trade prevention modes chosen by the newer order. CME Group's documentation for Globex describes FIFO and pro rata allocation, rounding down with remainders by FIFO, and mixed algorithms such as Allocation with a top order step. The FIX standard defines the time in force values most engines accept. For the whole venue around the engine (gateways, opening auctions, market data feeds and failover), see the stock exchange guide.
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.
Event Sourcing
advanced / distributed systems core
State Machine
advanced / distributed systems core
Write-Ahead Logging
advanced / consistency models
Snapshotting
advanced / consistency models
Skip Lists
intermediate / database types storage
Active-Passive Failover
advanced / reliability resilience
Actor Model
advanced / distributed systems core
Backpressure for Resilience
advanced / reliability resilience
Latency
foundation / core fundamentals
Multicast
foundation / load balancing proxies
Frequently asked questions
Go deeper
This guide is built on these 6 lessons. Each one teaches one piece of the order matching engine design above, step by step.
- Event SourcingSystem Design · distributed systems core · 30 min
- State MachineSystem Design · distributed systems core · 25 min
- Write-Ahead LoggingSystem Design · consistency models · 17 min
- SnapshottingSystem Design · consistency models · 16 min
- Skip ListsSystem Design · database types storage · 20 min
- Active-Passive FailoverSystem Design · reliability resilience · 32 min
Practice with 770 system design lessons
Lifetime access for ₹1,777 in India or $49 elsewhere. Interactive diagrams, quizzes, and 20 capstone projects to practise on.
This one question is not enough for the interview.
Get all 200 questions in the System Design and Distributed Systems pack
Each question has why it is asked, a full answer, what the interviewer listens for, the follow-ups, the common mistakes and a diagram (268 diagrams in all). A PDF you keep, junior to staff level.
India: UPI, cards or netbanking through Razorpay. Elsewhere: card or PayPal. Download at once from My packs.
NEWSystem Design Hands-On
Included in the System Design course: real systems, built live
Postgres Sharding: Complete Hands-On (PgBouncer + Citus). 2 h 36 min and 20 chapters: one PostgreSQL loaded until it breaks, then four shards behind a router you write, PgBouncer and Citus, with the 139-page slide deck. The first 15 minutes are free to watch.

