At the centre of every exchange sits the matching engine — the program that takes the stream of incoming orders and decides who trades with whom, at what price. It’s conceptually simple, which is part of its beauty: a couple of fair rules, applied relentlessly, millions of times a second. You’ll never run an exchange, but understanding the matching engine pays off twice: it demystifies why the market behaves as it does when you trade, and the same logic powers the fill simulation in your own backtester (Lesson 10). So we’ll understand it, and build a small one.
Price-Time Priority: the Two Rules
The matching engine answers one question over and over: when a new order arrives, which resting orders does it trade against, and in what order? Virtually all modern exchanges answer with price-time priority, which is just two rules.
Walk through the diagram’s example. A BUY 12 @ 101 arrives (a limit order: buy up to 12 units, paying no more than 101). The resting asks are: order A (10 @ 101.00), order B (8 @ 101.00), order C (15 @ 102.00). The engine applies:
- Better price wins. Among sellers, the lowest ask is the best for a buyer. Both A and B are at 101.00 (reachable at our limit); C is at 102.00 (too expensive for a 101 limit), so C is untouched.
- At the same price, earlier wins (time priority). A and B are both at 101.00, so the one that arrived first fills first. A arrived earlier, so it fills first.
Result: the buy fills 10 from A (exhausting it) and then 2 from B (which now has 6 left resting). C never trades. If the incoming buy had been for more than A+B could supply at 101, the remainder would rest in the book as a new bid at 101, waiting for future sellers — the order became liquidity. (A market buy, by contrast, would keep taking the next-best ask — including C at 102 — until filled, which is how market orders “walk the book” and slip.)
That’s the whole mechanism. Everything else — complex order types, auctions, circuit breakers — is elaboration on these two rules.
Why This Matters to You as a Trader
Understanding matching isn’t academic; it directly explains costs and behaviours you’ll meet:
- Why market orders slip. A market order takes the best price, then the next, then the next, until it’s filled — “walking the book.” On a thin book (little depth, Lesson 1), that walk reaches worse and worse prices fast, and you pay slippage (Lesson 12). The matching engine is literally where your slippage happens.
- Why queue position matters. At a given price, time priority means earlier orders fill first. For a market maker, being near the front of the queue at the best bid/ask is valuable — it’s the difference between getting filled and watching the price move away. This is one reason the fast players obsess over latency (Lessons 6–7): to get their quote in earlier.
- Why big orders move the price. A large order consumes multiple price levels of depth, pushing the price as it goes — “market impact.” Knowing the book’s depth (L2 data, Lesson 3) lets you estimate this before you trade, and is why large players slice orders into smaller pieces.
- Why your limit order might not fill. If you post a passive limit order, you’re now in the queue behind everyone already there at your price. You fill only when enough trades happen at your level to reach you — which may never come if the market moves away.
Every one of these is a direct consequence of price-time priority. Internalise the two rules and market microstructure stops being mysterious.
Building a Small Matching Engine
The best way to really understand it is to build one — and it’s the core of your backtester’s fill logic. A simple, correct design:
- Keep two sides: bids and asks.
- Each side is a set of price levels.
- Each price level holds a time-ordered queue (FIFO) of resting orders — first in, first out, which gives you time priority for free.
- To match an incoming order, repeatedly take the best price level on the opposite side (highest bid for an incoming sell, lowest ask for an incoming buy), and fill from the front of its queue until the incoming order is satisfied or no more matching prices remain. Anything left over rests in the book on its own side.
I implemented exactly this in pure Python and measured it: it processed 200,000 limit orders in 242 ms — about 825,000 orders per second, ~1.2 microseconds per order — and correctly handled the walk-the-book and resting-remainder cases from the example above. That’s plenty fast for a backtester and for understanding; a real exchange does the same thing in C++ (Lesson 5) with carefully chosen data structures, hitting far higher rates with deterministic microsecond latency (consistency matters as much as speed for an exchange).
A few engineering notes if you build one seriously:
- Data structures decide speed. You want O(1)-ish access to the best price and to each level’s queue. Real engines use specialised structures (sorted price arrays, intrusive linked lists per level, preallocated pools) to avoid per-order allocation — the same no-garbage, cache-friendly thinking as Lesson 5’s ring buffers.
- Determinism matters. An exchange must match in a reproducible, fair order — which is why matching engines are usually single-threaded per instrument (no locks, no races — again Lesson 5), processing one order at a time from a queue.
- For a backtester, simulate fills honestly. Use the real book depth and price-time rules to decide what your order would have filled at — and build in slippage and the fact that your own order would have had queue position. Optimistic fill assumptions are a classic backtest lie (Lesson 10).
Pitfalls to Avoid
- Thinking the exchange gives you a “fair” single price. You trade against the book by price-time priority; a big or market order gets multiple prices (slippage).
- Ignoring queue position. At your price you’re behind everyone already there; a passive limit may never fill. Time priority is real.
- Assuming limit orders always fill. They fill only if trading reaches your level. The market can move away and leave you unfilled.
- Per-order allocation in a hot matcher. Allocating objects per order creates garbage and jitter (Lesson 5). Preallocate and reuse.
- Optimistic fills in your backtester. Simulate fills using real depth and price-time rules, with slippage — not “I’d get the mid price.” Optimism here is a lie that costs real money (Lesson 10).
- Multithreading a single instrument’s matching. It must be deterministic and ordered; keep it single-threaded per instrument (Lesson 5).
Quick Recap
- A matching engine applies price-time priority: better price wins; at the same price, earlier order wins.
- An incoming order fills against the best opposite prices (walking the book), and any unfilled remainder rests as new liquidity.
- This explains real trading costs: market orders slip walking a thin book, queue position decides passive fills, and big orders move the price.
- A simple engine is price levels of time-ordered (FIFO) queues; my pure-Python version did ~825k orders/sec, and real exchanges do it in C++ with microsecond determinism.
- The same matching logic powers your backtester’s fill simulation — simulate fills honestly (Lesson 10). Educational, not financial advice.
Next lesson: making this fast for real. We’ll measure ring buffers vs lock-based queues, the latency cost of thread locks, and the stark difference between Python and C++ on the hot path.