You already received the conceptual brief earlier — what an order book is, the two sides, price-time priority, the three operations, matching, market orders, integer prices. This page turns that into a precise, buildable specification. Keep it open in one pane and your editor in the other.
1 · The data model
Define plain structs for the data and a class for the book (Module 04: structs for data, class for invariants).
enum class Side { Buy, Sell };
enum class OrderType { Limit, Market };
struct Order {
uint64_t id; // unique
Side side;
OrderType type;
int64_t price; // in ticks/cents; 0 for market
uint32_t quantity; // original size
uint32_t filled; // amount executed so far
uint64_t timestamp; // for time priority
uint32_t remaining() const; // quantity - filled
};
struct Trade {
uint64_t buy_order_id;
uint64_t sell_order_id;
int64_t price; // the RESTING order's price
uint32_t quantity;
uint64_t timestamp;
};2 · The container choices (justify each)
These are the decisions an interviewer will drill you on. You must be able to defend every one (Module 07).
- Bids:
std::map<int64_t, PriceLevel, std::greater<int64_t>>— highest price atbegin(). - Asks:
std::map<int64_t, PriceLevel>— lowest price atbegin(). - Each price level: a FIFO queue of resting orders in a
std::list— stable addresses, O(1) erase. - ID index:
std::unordered_map<uint64_t, /*location*/>— O(1) cancel-by-id.
Matching always needs the best price → sorted map, O(1) at begin(). Cancel needs to find an arbitrary order by id → hash map, O(1). The list at each level gives both FIFO ordering and stable pointers so the id index never dangles when neighbors are added or removed. Two-plus containers, each doing exactly the job its complexity suits.
3 · The three operations
add_order
- If it's a limit order, first try to match it against the opposite side (see below).
- If any quantity remains after matching, insert the remainder as a resting order: find/create its price level, append to the level's list, and record its location in the id index.
- A market order matches greedily against the opposite side and is never rested; if the opposite side empties first, the unfilled remainder is cancelled/rejected (your choice — document it).
cancel_order
- Look up the id in the index (O(1)). If absent, return false.
- Erase the order from its price level's list (O(1) via the stored iterator/pointer).
- If the level is now empty, erase the level from the map. Remove the id from the index.
match (the engine)
- A buy at price P can trade against asks with price ≤ P; a sell at price P against bids with price ≥ P.
- Walk the opposite side from
begin()(best price). At each level, fill orders front-to-back (time priority). - Each fill: quantity = min(incoming.remaining(), resting.remaining()). Emit a
Tradeat the resting order's price. Update bothfilledcounts. - Remove fully filled resting orders (and empty levels). Stop when the incoming order is filled or no acceptable price remains.
4 · Market-data interface
After any operation the book must answer, cheaply:
best_bid(),best_ask()—begin()of each map (or empty).spread()— best_ask − best_bid.depth_at(price)— total resting quantity at a level.top_n(n)— the top N levels per side for a snapshot.
5 · Project layout
orderbook/
├── CMakeLists.txt
├── include/
│ ├── order.h // Order, Trade, enums
│ ├── price_level.h // FIFO list at one price
│ └── order_book.h // the book + matching engine
├── src/
│ ├── order_book.cpp
│ └── main.cpp // hardcoded scenarios that print trades
├── test/
│ └── test_order_book.cpp // correctness checks
└── bench/
└── benchmark.cpp // throughput + p50/p95/p996 · Test plan (prove it's correct before you optimize)
- Add a resting buy, then a crossing sell → one trade at the resting price, correct quantities.
- Partial fill: incoming larger than resting → resting removed, incoming rests with remainder.
- Multiple levels: a large order sweeps several price levels in the right order.
- Time priority: two orders at the same price fill in arrival order.
- Cancel: add then cancel; confirm it no longer matches and the level is cleaned up.
- Empty-book market order: handled per your documented policy.
- Run the whole suite under
-fsanitize=address,undefined— zero errors.
7 · The benchmark
- Generate 1,000,000 random operations (mostly adds, some crossing, some cancels).
- Time the batch: report ns/op and ops/sec.
- Record per-op latencies, sort, and print p50/p95/p99.
- Put the numbers and your CPU/OS/compiler flags in the README.
8 · README checklist (what the recruiter reads)
- One-paragraph description: what it is, what it does.
- Build instructions (
cmake -S . -B build && cmake --build build). - The design decisions section: why map, why list, why unordered_map — in your words.
- Benchmark results with hardware.
- "Verified under AddressSanitizer and UBSan."
Types first → single price level with FIFO → book with add + resting (no matching) → matching engine → cancel → market data → tests (get them green under ASan) → only then benchmark and optimize. Correct first, fast second. Never optimize code you haven't proven correct.
Loading starter code…