Implement an in-memory order book API
Company: Citadel
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of in-memory data structures and API design for maintaining ordered collections with FIFO semantics, correctness of state management, and algorithmic complexity considerations. It is commonly asked in Coding & Algorithms interviews to assess practical implementation skills under constraints (e.g.
Constraints
- Up to 2 * 10^5 operations
- price and qty are positive integers whenever provided in an add operation
- new_qty is a non-negative integer in modify
- order_id is unique for each add operation
- Target complexity is around O(log M) amortized per add/query, where M is the number of distinct price levels on one side
- Memory usage should be O(N), where N is the number of live orders
Examples
Input: ([('best_bid',), ('add', 1, 'BUY', 101, 7), ('add', 2, 'SELL', 105, 7), ('best_bid',), ('best_ask',), ('top_of_book',)],)
Expected Output: [None, (101, 7), (105, 7), ((101, 7), (105, 7))]
Explanation: Initially there are no BUY orders, so best_bid is None. After adding one BUY and one SELL order, the best bid is (101, 7), the best ask is (105, 7), and top_of_book returns both.
Input: ([('add', 1, 'BUY', 100, 5), ('add', 2, 'BUY', 99, 8), ('modify', 1, 2), ('best_bid',), ('cancel', 1), ('best_bid',), ('best_ask',)],)
Expected Output: [(100, 2), (99, 8), None]
Explanation: Order 1 is reduced from 5 to 2, so the best bid stays at price 100 with quantity 2. After canceling order 1, the next best BUY level is 99 with quantity 8. There are no SELL orders.
Input: ([],)
Expected Output: []
Explanation: No operations means no query results.
Input: ([('add', 1, 'BUY', 100, 3), ('add', 2, 'BUY', 100, 4), ('add', 3, 'BUY', 99, 10), ('best_bid',), ('add', 4, 'SELL', 105, 2), ('add', 5, 'SELL', 105, 3), ('best_ask',), ('modify', 1, 1), ('modify', 4, 0), ('top_of_book',)],)
Expected Output: [(100, 7), (105, 5), ((100, 5), (105, 3))]
Explanation: BUY orders at price 100 aggregate to 7. SELL orders at 105 aggregate to 5. After modifying order 1 from 3 to 1 and canceling order 4 via modify(..., 0), the top of book becomes BUY (100, 5) and SELL (105, 3).
Input: ([('cancel', 99), ('modify', 42, 5), ('add', 1, 'SELL', 101, 2), ('add', 2, 'SELL', 100, 1), ('add', 3, 'SELL', 100, 4), ('best_ask',), ('cancel', 2), ('best_ask',), ('cancel', 3), ('best_ask',), ('modify', 1, 0), ('best_ask',)],)
Expected Output: [(100, 5), (100, 4), (101, 2), None]
Explanation: Missing cancel/modify calls do nothing. SELL price 100 starts with total 5, then drops to 4 after canceling one order, then disappears entirely after canceling the remaining order at 100, revealing 101 as the next best ask. Finally, modifying the last order to quantity 0 removes it.
Hints
- Use a hash map from order_id to the order's current side, price, quantity, and insertion timestamp so cancel and modify are fast.
- Track total quantity at each price level, and use a min-heap for asks plus a max-heap for bids (store negative prices). Lazy deletion is useful when a price level becomes empty.