−65% Most banks now $169 $59.
Coding · 10 min read

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 priorityLoses ordering within a price levelSorted levels with FIFO queues, oldest fills first
MatchingMishandles partial fills or crossed bookFills min quantity, advances both sides, stops when uncrossed
CancellationScans the whole bookOrder-id index for direct removal, cleans empty levels
Depth and quotesRecomputes best by scanningO(1) best bid/ask, aggregated depth per level
ExtensibilityRewrites the engine for new order typesLayers stop, iceberg, and multi-venue onto the book
Latency awarenessAssumes TreeMap is fast enoughNames array levels, object pooling, cache layout
Edge casesBreaks on empty book or full fillHandles 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.