Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Quant developer HFT course · Module 9: Trading systems

Designing an order book

Lesson · 14 min

The question

Design the order book that a feed handler maintains from a market-by-order feed. What operations does it need, and what data structures would you use?

What a book is

A limit order book holds all resting orders for one instrument, grouped by side and price. Each price with at least one order is a level, with a total quantity. On each side, orders at the same price are queued in time order: the first to arrive trades first. That rule is price-time priority: better price first, then earlier time.

bidspriceasks100.7542100.5018100.257100.001299.753099.5055spread: one tick
A price ladder: asks above, bids below, quantity at each level. The best bid is 100.00 and the best ask 100.25; with a tick size of 0.25 the spread is one tick.

The operations

A market-by-order feed sends events for individual orders. The book must support:

OperationTriggered byFrequency
add(order id, side, price, qty)new resting ordervery high
reduce or execute(order id, qty)partial cancel or fillhigh
delete(order id)cancel or full fillvery high
modify(order id, price, qty)replace, often delete plus addmedium
best bid / best askstrategy, every updatevery high
depth: top N levelsstrategy, pricinghigh
queue position of our own ordersome strategiesmedium

Two observations drive the design. Most activity is near the top of the book: adds, cancels and fills cluster within a few ticks of the best prices. And every event except add identifies the order only by its id, so finding an order by id must be fast.

The id map

A hash map from exchange order id to the order's handle in a pool (Modules 2 and 3). Open addressing, reserved for the expected peak number of live orders. Every delete, execute and modify starts here.

Levels: an array indexed by price

Prices are integers in ticks (Module 10). Near the touch, levels are dense: almost every tick has orders. So each side can keep its levels in an array indexed by price - base, where base is a reference price near the current market:

headnextnextheadticks 400emptyticks 401emptyticks 402qty 30ticks 403emptyticks 404qty 12ticks 405emptyorder 1710order 3115order 525order 812
Inside one side of the book. An array of levels indexed by price in ticks from a reference; each non-empty level holds its total quantity and a time-ordered queue of orders, linked through pool indices.
struct Level {
    std::int64_t qty = 0;           // total resting quantity
    std::uint32_t count = 0;        // number of orders
    std::uint32_t head = NIL, tail = NIL;   // FIFO of orders, as pool indices
};

struct Side {
    std::int64_t base;              // price, in ticks, of levels_[0]
    std::vector<Level> levels_;     // fixed size, e.g. a few thousand ticks
    std::int64_t best = NONE;       // index of the best non-empty level
};

Finding a level is one subtraction and one array access. Iterating the top N levels walks the array from the best index, skipping empty slots. Keeping best up to date is the one subtle part: on an add at a better price, it moves; on deleting the last order at the best level, the code scans for the next non-empty level, which is usually a few slots away because the book is dense near the top.

Prices far from the market, outside the array, are rare. They go to an overflow structure, such as a sorted vector or a std::map, or the array is recentred when the market moves far enough.

Orders at a level: an intrusive FIFO

Each order lives in a pool slot and carries its own prev and next links as pool indices:

struct Order {
    std::uint64_t exch_id;
    std::int64_t price;             // ticks
    std::int32_t qty;
    std::uint8_t side;
    std::uint32_t prev = NIL, next = NIL;   // links within its level
};

Adding appends at the level's tail: O(1). Deleting unlinks the order from wherever it is in the queue: O(1), given the handle from the id map. Executing reduces quantity and, at zero, deletes. No allocation, no scan.

Putting the update together

void Book::add(std::uint64_t id, Side& s, std::int64_t px, std::int32_t qty) {
    std::uint32_t h = pool_.acquire();
    Order& o = pool_[h];
    o = Order{id, px, qty, s.id};
    Level& lv = s.level_at(px);           // array slot, or overflow if far away
    lv.append(h, pool_);                  // tail of the FIFO
    lv.qty += qty; lv.count++;
    s.maybe_improve_best(px);
    ids_.insert(id, h);
}

void Book::remove(std::uint64_t id) {
    std::uint32_t h = ids_.take(id);      // find and erase in the id map
    Order& o = pool_[h];
    Side& s = side_of(o);
    Level& lv = s.level_at(o.price);
    lv.unlink(h, pool_);
    lv.qty -= o.qty; lv.count--;
    if (lv.count == 0) s.on_level_emptied(o.price);   // may move best
    pool_.release(h);
}

Every operation is a constant number of array accesses, one hash lookup and no allocation. The hot data for the top levels and their recent orders fits in cache.

Variants

  • A market-by-price feed sends level updates directly, so the book is just the level arrays, without orders or an id map.
  • A trading strategy's own view may only need the top few levels, kept in a tiny fixed array.
  • Some designs keep levels in a sorted vector of (price, qty) per side, best price at the end, which is simple and fast when depth is small.

In the interview

Start from the operations and their frequencies, then design for them: hash map from id to pooled orders, tick-indexed level arrays near the touch with an overflow for the rest, intrusive FIFOs for time priority, and a tracked best index. Mention what you would measure, and that std::map<price, level> is a correct first version you would replace after profiling.

Check yourself

Question 1 of 3

What does price-time priority mean?

Question 2 of 3

Why index price levels with an array rather than a std::map?

Question 3 of 3

A delete message carries only an order id. How does the book remove the order in O(1)?

In the interview

Say your own answer out loud first, then open the one we would give.

How would you implement an order book?

Orders in a preallocated pool, found by exchange id through an open-addressing hash map. Each side keeps price levels in an array indexed by ticks from a reference price, with an overflow structure for far prices; each level holds total quantity and an intrusive FIFO of its orders for time priority. Adds, deletes and executions are O(1) with no allocation, and the best price is tracked as an index.

How do you keep track of the best bid and ask efficiently?

Keep the index of the best non-empty level. An add at a better price moves it directly; removing the last order at the best level scans to the next non-empty level, which is usually close because the book is dense near the top.

What changes for a market-by-price feed?

The feed reports level quantities directly, so there are no individual orders or id map, just the level arrays updated in place. It is simpler and cheaper, but you lose queue positions and order-level detail.