arrow_backBACK TO LOW-LATENCY TRADING SYSTEMS & ALGO TRADING — FROM THE ORDER BOOK TO A LIVE RETAIL ALGO
Lesson 04Low-Latency Trading Systems & Algo Trading — From the Order Book to a Live Retail Algo6 min read

The Matching Engine

October 28, 2026

TL;DR

A matching engine takes incoming orders and matches them against the resting order book by two rules: BETTER PRICE wins, and at the same price, the EARLIER order wins (price-time priority). A buy order fills against the lowest asks it can reach; whatever it can't fill at its limit rests in the book as a new bid. Understanding this makes you a better trader: it's why your market order 'walks the book' and slips on thin depth, why being early in the queue at a price matters to market makers, and why large orders move the price. A simple engine keeps each side as price levels, each level a time-ordered queue (FIFO) of orders — I built one in pure Python that processed 200,000 orders in 242 ms (~825k orders/sec, 1.2 us/order), and real exchanges do this in C++ (Lesson 5) at far higher rates with microsecond determinism. You won't run an exchange, but building a small matcher demystifies the market and powers your own backtester's fill simulation. Educational, not financial advice.

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.

How the matching engine works: price-time priority, best price first then first-come-first-served. Incoming: BUY 12 at 101. Resting asks sorted by price then time: 101.00 order A 10 matches first (best price); 101.00 order B 8 matches second (same price, later); 102.00 order C 15 untouched (worse price). Result of the BUY 12 at 101: fill 10 from order A (best price, oldest) then 2 from order B (same price, next in line); order B now has 6 left; order C never trades (its 102 is worse than the 101 limit). Two rules decide everything: better price wins; at the same price, earlier order wins

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:

  1. 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.
  2. 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

  1. 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).
  2. Ignoring queue position. At your price you’re behind everyone already there; a passive limit may never fill. Time priority is real.
  3. Assuming limit orders always fill. They fill only if trading reaches your level. The market can move away and leave you unfilled.
  4. Per-order allocation in a hot matcher. Allocating objects per order creates garbage and jitter (Lesson 5). Preallocate and reuse.
  5. 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).
  6. 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.