Implement Price-Time Order Matching
Two SigmaSoftware Engineer · Technical ScreenLast updated
Design and implement an in-memory limit order book matching engine supporting buy and sell orders with price-time priority (FIFO) and order cancellation.
Problem Statement
Implement an in-memory matching engine for a limit order book operating under price-time priority (FIFO).
When a new limit order enters the market:
- It attempts to cross against existing resting orders on the opposite side of the book.
- A BUY order matches against resting SELL orders where
resting_sell_price = incoming_sell_price, starting from the highest buy price.
- A BUY order matches against resting SELL orders where
- Tie-breaking (Time Priority): If multiple resting orders have the same price, the order submitted earliest must be filled first.
- Execution Price: The match executes at the price of the resting order (maker price), not the incoming order.
- Partial Fills: An order can be partially filled across multiple counter-orders until its quantity reaches zero or no valid matches remain. Any remaining unfilled quantity is placed in the book as a resting order.
Implement the OrderMatcher class with the following methods:
class OrderMatcher:
def __init__(self):
pass
def add_order(self, order_id: int, side: str, price: int, quantity: int) -> list[tuple[int, int, int, int]]:
"""
Submits a new limit order.
side is either 'BUY' or 'SELL'.
Returns a list of executed trades in the order they occur:
[(buy_order_id, sell_order_id, match_price, match_quantity), ...]
"""
pass
def cancel_order(self, order_id: int) -> bool:
"""
Cancels a resting order if it exists on the book.
Returns True if the order was found and removed, False otherwise.
"""
pass
Example
m = OrderMatcher()
# 1. Add BUY order 1: price 100, qty 10
m.add_order(1, "BUY", 100, 10)
# Output: [] (rests in book)
# 2. Add BUY order 2: price 101, qty 5
m.add_order(2, "BUY", 101, 5)
# Output: [] (rests in book)
# 3. Add SELL order 3: price 99, qty 8
# Best buy is order 2 (price 101, qty 5), followed by order 1 (price 100, needs 3)
m.add_order(3, "SELL", 99, 8)
# Output: [
# (2, 3, 101, 5), # Order 2 fully filled at resting price 101
# (1, 3, 100, 3) # Order 1 partially filled (7 left) at resting price 100
# ]
# 4. Cancel order 1
m.cancel_order(1)
# Output: True (remaining 7 qty removed)
# 5. Cancel non-existent / filled order 2
m.cancel_order(2)
# Output: False
Constraints
1 <= order_id <= 10^9(unique across alladd_ordercalls)side in ("BUY", "SELL")1 <= price <= 10^61 <= quantity <= 10^6- Up to
20,000total calls toadd_orderandcancel_order.
Edge Cases
- Incoming order matches against multiple price levels and multiple orders at the same price level.
- Orders that match completely vs. orders that leave residual quantities.
- Canceling an order that has already been fully matched or never existed.
Hints
Hint 1
Use two priority structures (e.g., heaps or balanced trees) to track resting buy orders (max-heap by price) and sell orders (min-heap by price).
Hint 2
To preserve time priority at identical prices, pair price with a monotonically increasing timestamp or sequence number, or maintain a FIFO queue per price level.
Hint 3
For efficient cancellations, consider lazy deletion where canceled orders are marked in a lookup hash map and discarded when popped from the top of the queue.
Follow-up questions
- How would you optimize the data structure to achieve O(1) order cancellation if lazy deletion is not acceptable due to memory pressure?
- How would you support Market Orders (orders with no limit price) and Stop-Loss orders in this matching engine?
- If this matching engine had to process millions of orders per second in a real-world multi-threaded environment, how would you design the concurrency and synchronization model?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.