Citadel's High-Performance Order Book Question
Citadel's coding rounds put you straight into the matching engine. You build an order book that keeps price-time priority and matches crossing orders, then the interviewer layers on the order types a real exchange supports. Here is the full question, Java for both parts, and a detailed rubric.
The question
Implement a high-performance order book. Support limit and market orders, maintain price-time priority, and implement addOrder, cancelOrder, matchOrders, getBestBid, getBestAsk, and getMarketDepth. Target a million-plus orders per second with sub-microsecond matching.
Part 2: extend it to support stop-loss orders, iceberg orders, and multiple exchanges.
Part 1 · The matching book
Two sorted maps of price levels, bids descending and asks ascending, each level a FIFO queue so older orders fill first. A side index by order id makes cancellation cheap. Matching crosses the best bid and ask while they overlap.
import java.util.*;
class OrderBook {
enum Side { BUY, SELL }
static final class Order {
final long id, price; long qty; final Side side; final long ts;
Order(long id, Side side, long price, long qty, long ts) {
this.id = id; this.side = side; this.price = price; this.qty = qty; this.ts = ts;
}
}
private final TreeMap<Long, Deque<Order>> bids = new TreeMap<>(Collections.reverseOrder());
private final TreeMap<Long, Deque<Order>> asks = new TreeMap<>();
private final Map<Long, Order> index = new HashMap<>();
void addOrder(Order o) {
TreeMap<Long, Deque<Order>> book = (o.side == Side.BUY) ? bids : asks;
book.computeIfAbsent(o.price, k -> new ArrayDeque<>()).addLast(o); // time priority
index.put(o.id, o);
}
void cancelOrder(long id) {
Order o = index.remove(id);
if (o == null) return;
TreeMap<Long, Deque<Order>> book = (o.side == Side.BUY) ? bids : asks;
Deque<Order> level = book.get(o.price);
if (level != null) { level.remove(o); if (level.isEmpty()) book.remove(o.price); }
}
void matchOrders() { // fill while the book is crossed
while (!bids.isEmpty() && !asks.isEmpty() && bids.firstKey() >= asks.firstKey()) {
Deque<Order> bidLevel = bids.firstEntry().getValue();
Deque<Order> askLevel = asks.firstEntry().getValue();
Order bid = bidLevel.peekFirst(), ask = askLevel.peekFirst();
long fill = Math.min(bid.qty, ask.qty);
bid.qty -= fill; ask.qty -= fill;
if (bid.qty == 0) { bidLevel.pollFirst(); index.remove(bid.id); if (bidLevel.isEmpty()) bids.pollFirstEntry(); }
if (ask.qty == 0) { askLevel.pollFirst(); index.remove(ask.id); if (askLevel.isEmpty()) asks.pollFirstEntry(); }
}
}
Long getBestBid() { return bids.isEmpty() ? null : bids.firstKey(); }
Long getBestAsk() { return asks.isEmpty() ? null : asks.firstKey(); }
List<long[]> getMarketDepth(Side side, int levels) { // {price, totalQty} per level
TreeMap<Long, Deque<Order>> book = (side == Side.BUY) ? bids : asks;
List<long[]> depth = new ArrayList<>();
for (Map.Entry<Long, Deque<Order>> e : book.entrySet()) {
if (depth.size() == levels) break;
long total = 0;
for (Order o : e.getValue()) total += o.qty;
depth.add(new long[]{ e.getKey(), total });
}
return depth;
}
}
A TreeMap is clear but not sub-microsecond. The optimization to discuss is an array of price levels with intrusive linked lists, pooled order objects to avoid allocation, and tight cache layout. State the clean version first, then describe that path.
Part 2 · Advanced order types
Each extension is a layer on top of the book, not a rewrite. Describe the data each one adds and where it hooks into matching.
- Stop-loss. Keep a separate trigger structure keyed by stop price. On every trade, check whether the last price crossed any stop; if so, inject the parked order into the book as a market or limit order.
- Iceberg. Store a display quantity and a hidden reserve. Only the display quantity rests visibly; when it fills, replenish from the reserve and re-queue at the back of the level, which correctly forfeits time priority on each refresh.
- Multiple exchanges. Keep one book per venue and a consolidated top-of-book across venues; route to the best price, accounting for per-venue latency and fees.
// Iceberg replenishment, triggered when the visible slice is exhausted.
void onIcebergFilled(Iceberg ico) {
if (ico.reserve == 0) return;
long slice = Math.min(ico.displaySize, ico.reserve);
ico.reserve -= slice;
Order visible = new Order(nextId(), ico.side, ico.price, slice, now());
addOrder(visible); // re-enters at the BACK of its price level (loses time priority)
}
How Citadel scores it
| Dimension | Weak | Strong |
|---|---|---|
| Price-time priority | Loses ordering within a price level | Sorted levels with FIFO queues, oldest fills first |
| Matching | Mishandles partial fills or crossed book | Fills min quantity, advances both sides, stops when uncrossed |
| Cancellation | Scans the whole book | Order-id index for direct removal, cleans empty levels |
| Depth and quotes | Recomputes best by scanning | O(1) best bid/ask, aggregated depth per level |
| Extensibility | Rewrites the engine for new order types | Layers stop, iceberg, and multi-venue onto the book |
| Latency awareness | Assumes TreeMap is fast enough | Names array levels, object pooling, cache layout |
| Edge cases | Breaks on empty book or full fill | Handles empty book, exact fills, self-cleaning levels |
This is one question of 9 pages
Get the full Citadel question bank
The full Citadel bank carries the trading-systems and quant coding rounds, each problem with its follow-ups and a rubric, compiled from people familiar with the process and cross-verified across sources.
Citadel interview FAQ
What does the Citadel coding interview ask?+
Trading-systems problems: order books, matching engines, market-data handlers, and short-horizon prediction. The order book question tests correct price-time priority, matching, cancellation, and how cleanly your design extends to advanced order types.
How fast does a Citadel order book need to be?+
The prompt targets a million-plus orders per second and sub-microsecond matching. In an interview you implement the clean version with the right structures (sorted price levels, FIFO queues), then discuss the cache-friendly, allocation-free variant a production engine uses.
What language should I use for the Citadel interview?+
C++ is most common for trading systems, but Java or another mainstream language is accepted. This guide uses Java for readability; the data-structure choices carry over directly to C++.
How is the Citadel coding round scored?+
On correct price-time priority and matching, efficient cancel and best-bid/ask lookups, a clean extension to stop-loss and iceberg orders, latency-aware data-structure choices, and edge cases like partial fills and an empty book.
Where can I find real Citadel interview questions?+
Pichup maintains a Citadel question bank compiled from people familiar with the process, with the trading-systems and quant rounds, their follow-ups, and rubrics. The question in this guide is one of them.