The One Rule Interviewers Expect in a Stock Trading System: Price–Time Priority


The One Rule Interviewers Expect in a Stock Trading System: Price–Time Priority
In any stock trading system the matching engine must enforce price–time priority. This is not optional — it’s the market’s fairness contract.
At its core the rule is simple:
- Better price wins. For bids, a higher price is better. For asks, a lower price is better.
- If prices tie, earlier timestamp wins — first in, first out (FIFO) within the same price level.
Because this is the law of fairness in matching, your design must guarantee both parts: quick access to the best price and strict FIFO ordering for orders at the same price.
Object-Oriented Design implications
An OrderBook cannot be a simple flat list of orders. Instead model two sides — bids and asks — each as a structure that:
- Keeps the best price immediately accessible (max for bids, min for asks).
- Preserves FIFO within each price level (queue semantics for orders that share the same price).
A common and effective representation is:
- price → queue(Order)
- a sorted index of prices to allow retrieving the best price quickly (e.g., a balanced tree or heap)
This yields efficient operations:
- Find best price: O(log P) (P = number of distinct price levels) using a tree map or heap.
- Enqueue/dequeue at a price level: O(1) using a linked queue (deque).
Typical data-structures by language
- Java: TreeMap> (bids use descendingKeySet; asks use ascendingKeySet).
- C++: std::map or std::set of price-level objects with std::deque for orders at each level.
- Python: sortedcontainers.SortedDict or bisect + list of price levels, with collections.deque for per-price queues.
Matching algorithm (high level)
When a new order arrives:
- Determine the opposite side (new bid matches existing asks; new ask matches existing bids).
- While the order has remaining quantity and there exists at least one compatible price on the opposite side:
- Pick the best price on the opposite side.
- Pop from the head of that price's queue (oldest order at that price).
- Execute a trade for the min of remaining quantities.
- If the price-level queue becomes empty, remove that price from the sorted index.
- If the incoming order still has remaining quantity, insert it at its price level's queue (newest at tail).
This preserves:
- Price priority: you always match against the best price first.
- Time priority: within a price you always consume the oldest order first.
Pseudocode
function match(incomingOrder): opposite = (incomingOrder.side == BUY) ? asks : bids while incomingOrder.qty > 0 and opposite.has_compatible_price(incomingOrder.price): bestPrice = opposite.best_price() queue = opposite.get_queue(bestPrice) restingOrder = queue.peek() tradeQty = min(incomingOrder.qty, restingOrder.qty) execute_trade(incomingOrder, restingOrder, tradeQty, tradePrice = restingOrder.price) decrease quantities accordingly if restingOrder.qty == 0: queue.pop() if queue.empty(): opposite.remove_price_level(bestPrice) if incomingOrder.qty > 0: sameSide = (incomingOrder.side == BUY) ? bids : asks sameSide.get_or_create_queue(incomingOrder.price).push(incomingOrder)
Example
- Existing asks: 100.50 → [Order A at t1], 101.00 → [Order B at t2]
- Incoming bid at 101.00 (t3): matches against 100.50 first because it's the best ask (lowest price). Within 100.50, Order A (t1) is matched before any later 100.50 orders.
Performance & concurrency notes
- Keep price-level queues lockable independently to reduce contention (per-price locks or lock-free queues where possible).
- Use a single matching thread for determinism in high-frequency engines; or partition by symbol to avoid cross-symbol locking.
- Persist trades and changes durably and in order to meet audit/regulatory needs.
Edge cases and testing
- Partial fills must leave correct quantities and timestamps.
- Orders with identical timestamps (rare) should have a deterministic tie-breaker (e.g., internal sequence number assigned on receipt).
- Verify behavior on order cancellation and replacement (ensure FIFO at price level still holds).
- Simulate high-throughput scenarios: many orders at same price, bursts, and out-of-order network delivery.
Summary
Price–time priority is the single non-negotiable rule in matching engines: best price first, then earliest order at that price. Implement an OrderBook as price→queue plus a sorted index of price levels to guarantee both fast best-price access and FIFO within a price level. This design is efficient, auditable, and preserves market fairness.


