Computer Science

Building a trading exchange in 200 lines

Modern financial exchanges move billions of dollars a day, yet the core matching engine comes down to a handful of data structures and a few unglamorous rules.

· 9 min read

Updated with images and the interactive order book.
Buy and sell orders flowing into a matching engine, with trades emerging On the left, two streams of order tickets flow rightward: teal buy orders above and crimson sell orders below. They converge on a single dark box in the centre labelled the matching engine, which holds a short while loop. On the right, a row of executed trades prints onto a tape, alternating teal and crimson. The picture says an exchange is two queues meeting at one comparison. BIDS AND ASKS IN TRADES OUT BUY 100 @ 101 BUY 200 @ 100 BUY 50 @ 99 SELL 150 @ 102 SELL 100 @ 103 MATCHING ENGINE while (bid >= ask) trade(); BEST PRICE · OLDEST FIRST 100 @ 101 150 @ 102 50 @ 101 200 @ 103 80 @ 102 THE TAPE Two queues meet at a single comparison. Everything else an exchange does is keeping that loop alive under load.

Most people picture a stock exchange as an impossibly complicated piece of software: a labyrinth of market makers, trading desks, regulators, risk systems and enough hardware to heat a mid-sized town. They are not entirely wrong. Modern venues are genuinely sophisticated, and the people who run them have my sympathy.

But buried underneath all of that sits a stubbornly simple idea. Buyers want to buy. Sellers want to sell. The exchange's only real job is to introduce the two. Everything else is detail, regulation and operational scar tissue.

I spent years working with front-office interfaces near the trading floor, close enough to the matching engines to respect them and far enough away to keep my sanity. The thing that surprised me, every time, was how small the actual heart of the machine turned out to be.

It's a queue.

Well. Several queues. Let's build one.

The simplest possible market

Imagine we trade a fictional stock called:

YORK

Our exchange receives two kinds of order. Buy orders:

Buy 100 shares @ £101

and sell orders:

Sell 100 shares @ £102

The first thing we need is somewhere to keep them.

const buyOrders = [];
const sellOrders = [];

Congratulations. You now own a stock exchange. Admittedly not a very good one, and the regulator would have questions, but the bones are there.

The order book

An order book is just a list of outstanding orders that nobody has matched yet. Suppose the following arrives:

BUY 100 @ 101
BUY 200 @ 100
BUY  50 @  99

The book becomes:

BUY SIDE

101 -> 100
100 -> 200
 99 ->  50

Notice the ordering. Higher buy prices sit at the top, for the obvious reason that a buyer offering £101 is more useful to us than one offering £99. The sell side works in reverse:

SELL SIDE

102 -> 150
103 -> 200
104 -> 100

Lower sell prices come first, because a seller asking £102 is more attractive than one asking £104. The exchange spends its entire life staring at the top of these two lists, waiting for them to overlap.

The first trade

Suppose we receive:

BUY 100 @ 105

and the sell side currently holds:

SELL 100 @ 102

The buyer is willing to pay more than the seller is asking, so the trade can happen on the spot. The buyer pays 102, the seller receives 102, and both walk away. No haggling, no auctioneer, no drama.

That is matching, and it is the whole reason the exchange exists. Strip away the compliance and the colocation and the marketing, and an exchange is a machine that does this one thing, very fast, without ever getting bored.

Price-time priority

Now it gets slightly more interesting. Imagine two traders submit the identical order:

BUY 100 @ 101
BUY 100 @ 101

Same price. Who gets filled first?

The answer is whoever arrived first. This is price-time priority, and it is the rule that keeps the whole thing fair enough to be legal. Price decides who wins; time breaks the ties. The matching policy fits on a beer mat:

Best price first

Oldest order first

Those two lines underpin an enormous slice of modern electronic trading. People build careers, and occasionally lose them, on the second one.

Representing an order

A single order doesn't need to carry much:

{
  id: 123,
  side: "BUY",
  quantity: 100,
  price: 101,
  timestamp: 1710000000
}

Nothing exotic. The hard part was never describing an order. The hard part is finding the right one quickly, over and over, while millions more are arriving.

Where arrays start to hurt

Our first instinct stored everything in arrays, which is fine for ten orders and a disaster for ten million. To find the best price in an array you have to walk the whole thing:

for (const order of orders) {
  // find best price
}

Every lookup scans the entire collection. At low volume nobody notices. As the book grows, that linear scan becomes the bottleneck that eats your exchange alive.

This is the point where the choice of data structure stops being academic and starts being the difference between a product and an outage.

Priority queues

Most matching engines reach for a priority queue, which is the data structure equivalent of always knowing who is next in line. Conceptually you want:

Highest BUY first

Lowest SELL first

A priority queue hands you the best available price immediately, without searching for it. The cost of finding the top order drops from a full scan:

O(n)

to something far more civilised:

O(log n)

That looks like a footnote until your exchange is taking hundreds of thousands of orders a second, at which point the gap between those two lines is the gap between a healthy venue and a smoking crater. Anyone who has watched a chess engine lean on the same trick to pick the best move out of millions will recognise the shape - the architecture of a chess engine is, underneath, the same obsession with never doing work you can avoid.

The matching loop

Here is the part that always disappoints people who were hoping for something grander. The core of the exchange, the bit the regulators worry about and the high-frequency firms pay millions to shave microseconds from, looks like this:

while (
  bestBuy &&
  bestSell &&
  bestBuy.price >= bestSell.price
) {
  executeTrade();
}

That's the engine. The whole conceptual thing. Everything past this point is optimisation, persistence and people in suits.

The loop asks one question, repeatedly:

Can these two people trade?

If yes, it trades and asks again. If no, it waits. You can play with exactly that loop below. Orders stream into both sides of the book, the best bid and the best offer drift towards each other, and whenever they cross the engine fires a trade onto the tape. Push a large order through and watch it sweep several price levels at once.

YORK · matching engine
bid 100ask 102spread 2last ·
pricesize
10060
9980
98100
97120
96140
95160
bids×asks
pricesize
10260
10380
104100
105120
106140
107160
tape waiting for a cross…
Best bid and best offer drift towards each other as orders arrive; whenever they cross, the engine prints a trade on price-time priority and decrements the resting size. The sweep buttons fire one large marketable order through the book so you can watch it eat several levels at once.

It is oddly soothing to watch, in the way that watching other people work usually is.

Partial fills

Real markets add a wrinkle the beer mat skipped. Suppose this:

BUY 500 @ 101

meets this:

SELL 100 @ 101

Only part of the order can execute. After the trade clears, what remains is:

BUY 400 @ 101

still sitting in the book, still holding its place in the queue. The engine has to decrement quantities, retire the order that got fully filled, and leave the survivor's time priority untouched, because shuffling it to the back of the line would break the fairness rule everyone is relying on.

This is where systems that looked trivial on the whiteboard begin to grow corners. None of it is conceptually hard. There is just a lot of it, and every piece has to be exactly right.

The performance problem

By now we have a working exchange. Unfortunately it is also slow, and finance cares about latency in a way almost no other industry does. A delay of:

1 millisecond

is not a rounding error here. It is the difference between getting filled and watching someone faster take your trade. When the prize for being early is real money, every microsecond between an order arriving and the trade printing becomes something worth fighting over.

So the questions stop being about correctness and start being about mechanical sympathy. Can we allocate less memory on the hot path? Can we avoid locks entirely? Can we lay the data out so the CPU stops stalling on cache misses? Can we keep the whole order book resident in cache rather than wandering off to main memory for every lookup?

"Does it work?" gives way to "how quickly, and how predictably, does it work?" That second word matters more than people expect: a fast engine that occasionally stutters is worse than a slightly slower one that never does. I wrote more about that obsession in latency budgets for trading UIs, where the same instinct shows up one layer further from the metal.

Why matching engines are quietly beautiful

I find matching engines genuinely lovely - and not only because they paid my mortgage for a while. They are one of the cleanest demonstrations of a truth that takes most engineers years to fully trust: systems that look impossibly complicated from the outside are very often built on a tiny, simple core.

The matching rules fit on a whiteboard. The complexity lives somewhere else entirely:

risk controls
fault tolerance
persistence
market data distribution
replication
recovery
monitoring
compliance

All of that is real, all of it is hard, and none of it changes the centre. The exchange spends its whole life answering the one question it started with: can these two orders trade?

The same shape, everywhere

Once you have seen a matching engine, you start noticing them in places that have nothing to do with finance. Ride-hailing apps match riders to drivers. Food delivery matches couriers to kitchens. Job marketplaces, dating apps, auction sites, cloud schedulers fitting workloads onto machines: all of them are variations on the same problem.

Two parties want to interact, and something in the middle has to decide who pairs with whom, on what terms, in what order. Trading exchanges are simply the version where the clock is unforgiving and the stakes are denominated in money, which is why they got the engineering attention first.

The actual lesson

The interesting thing about building an exchange turns out not to be the finance at all. It's the engineering posture.

From a distance, large systems look like monuments to complexity, and we treat them with a kind of superstitious awe. Up close, the core ideas are usually embarrassingly plain. Queues. Priority. Ordering. Matching. The sophistication accretes later, in layers, mostly to keep the simple core alive under load and regulation and failure.

The next time you read that some exchange cleared a few trillion dollars in a day, picture what actually sits at the dead centre of that machine. Two orders. A comparison. A trade. Repeated until the closing bell.

The most impressive systems are rarely built from complicated ideas. They are built from simple ideas, chosen carefully and executed with no tolerance for sloppiness whatsoever.

Further reading

Human (you) in the loop

New writing, now and then

Occasional notes on platform engineering, building dependable software and that constant buzz word we doom scroll past on LinkedIn! No cadence promised.

Prefer a reader? Subscribe via RSS.

← All Articles

Jump to

32 articles