Design a Stock Exchange
OOP design for a stock exchange covering order matching engine with price-time priority, order book management, limit and market orders, trade execution, and portfolio tracking.
The Problem
Your brokerage processes 50,000 orders per day across 200 listed stocks. The current system stores all orders in a flat database table and scans every open order whenever a new one arrives. At peak market hours, matching takes 800ms per order, and traders are losing money because prices move before their orders execute. Last quarter, a race condition caused two traders to buy the same shares, creating a phantom balance that took three days to reconcile.
Stock exchanges are harder than typical CRUD systems because correctness and ordering matter enormously. A matching engine that executes trades in the wrong sequence, misses a better-priced order, or allows partial fills without accurate bookkeeping can cause real financial losses. The system must enforce price-time priority (best price first, then earliest order at that price), handle both limit and market orders, support partial fills, and keep portfolios in sync with every trade.
Design the core classes for a stock exchange that handles order submission, order book management with price-time priority, a matching engine for limit and market orders, trade execution with partial fills, portfolio tracking, and order cancellation.
Requirements
Clarifying Questions
Before jumping into class design, ask questions to narrow the problem. Cover four areas: core actions, error handling, boundaries, and future extensions.
You: "What types of orders does the system support? Just limit and market, or also stop-loss, stop-limit, and others?"
Interviewer: "Start with limit and market orders. A limit order executes at a specific price or better. A market order executes immediately at the best available price. We can extend to stop-loss later."
Two order types with fundamentally different matching behavior. Limit orders rest in the order book if they cannot match immediately. Market orders must match immediately or be cancelled. That difference suggests a polymorphic order hierarchy or a Strategy for matching behavior.
You: "How does order matching work? What is the priority when multiple orders exist at the same price?"
Interviewer: "Price-time priority. For buy orders, the highest price has priority. For sell orders, the lowest price has priority. At the same price level, the order that arrived first gets filled first."
Price-time priority is the standard algorithm used by real exchanges like NYSE and NASDAQ. We need sorted data structures: buy side sorted by price descending, sell side sorted by price ascending. Within each price level, orders are processed in FIFO order.
You: "Can an order be partially filled? What happens to the remaining quantity?"
Interviewer: "Yes, partial fills are required. If a buy order for 100 shares matches a sell order for 60 shares, the buy order stays open with 40 shares remaining. Track both the original quantity and the filled quantity."
Partial fills introduce order states: OPEN, PARTIALLY_FILLED, FILLED, CANCELLED. Every match produces a Trade record, and the remaining quantity determines whether the order stays in the book.
You: "Can traders cancel open orders? What about modifying an existing order?"
Interviewer: "Cancellation is required. Modification is out of scope, but a common extension would be cancel-and-replace. You can only cancel orders that are OPEN or PARTIALLY_FILLED."
Cancellation requires looking up an order by ID and removing it from the order book. The order book must support efficient removal at any price level.
You: "Does each stock have its own order book, or is there a single shared book?"
Interviewer: "Each stock has its own independent order book. The exchange manages multiple order books, one per stock symbol."
One OrderBook per stock, owned by the StockExchange. This keeps matching logic isolated per symbol and avoids cross-stock locking.
You: "Should the system track user portfolios? What happens to holdings when a trade executes?"
Interviewer: "Yes. Each user has a portfolio showing how many shares they hold per stock plus their cash balance. When a trade executes, deduct cash from the buyer, deduct shares from the seller, and update both portfolios atomically."
Portfolio updates must be synchronized with trade execution. A trade that deducts shares from the seller but crashes before crediting the buyer would corrupt the system. Both sides must update together.
You: "Do we need to validate that a trader has sufficient funds or shares before placing an order?"
Interviewer: "Yes. A buy order should be rejected if the trader cannot afford it at the limit price times the quantity. A sell order should be rejected if the trader does not hold enough shares. Market orders are trickier since the final price is unknown, so validate against the best available price."
Pre-trade validation prevents phantom orders that could never execute. For limit orders, validation is straightforward (price times quantity). For market orders, check against the current best price in the book.
You: "Should the system notify traders when their orders are filled or partially filled?"
Interviewer: "Yes, fire events for order fills, partial fills, and cancellations. The notification delivery mechanism is out of scope, but the event system should be in place."
Event-driven notifications fit the Observer pattern. The matching engine fires trade events, and listeners (notification service, audit log, analytics) react independently.
Final Requirements
Functional Requirements:
- Submit limit orders (buy/sell) at a specified price and quantity
- Submit market orders that execute immediately at the best available price
- Match orders using price-time priority (best price first, then FIFO at same price)
- Support partial fills with accurate remaining-quantity tracking
- Cancel open or partially filled orders
- Track user portfolios (cash balance and stock holdings) with atomic updates on trade execution
- Validate sufficient funds (buy) or shares (sell) before accepting an order
Non-Functional Requirements:
- Thread safety for concurrent order submissions on the same order book
- Extensibility for new order types (stop-loss, iceberg) without modifying existing matching logic
- Event-driven architecture for trade notifications
Out of Scope:
- Market data feeds and real-time quotes
- Persistence and database storage
- Regulatory compliance (circuit breakers, trading halts)
- UI or API layer
- Settlement and clearing (T+2 settlement cycles)
Interview tip
In a real interview, writing these requirements on the whiteboard shows structure. Most candidates skip straight to classes. Spending two minutes on requirements saves ten minutes of backtracking later.
Example Inputs and Outputs
Scenario 1: Simple limit order match
- Trader A submits: SELL 100 shares of AAPL at $150 (limit)
- Trader B submits: BUY 100 shares of AAPL at $150 (limit)
- Expected: Trade executes at $150 for 100 shares. Both orders move to FILLED. Trader B's cash decreases by $15,000, Trader A's AAPL holdings decrease by 100, and vice versa.
Scenario 2: Partial fill
- Existing order: SELL 200 shares of GOOG at $100 (limit)
- Trader C submits: BUY 80 shares of GOOG at $100 (limit)
- Expected: Trade executes at $100 for 80 shares. Buy order is FILLED. Sell order moves to PARTIALLY_FILLED with 120 shares remaining.
Scenario 3: Market order sweeps multiple levels
- Order book for TSLA sell side: 50 shares at $200, 100 shares at $201
- Trader D submits: BUY 120 shares of TSLA (market)
- Expected: Two trades, one for 50 shares at $200 and one for 70 shares at $201. The sell order at $200 is FILLED. The sell order at $201 is PARTIALLY_FILLED with 30 shares remaining. Trader D's total cost is $24,070.
Try It Yourself
Try it yourself
Before reading the solution, spend 20 minutes sketching your own class diagram. Focus on the order book data structure and how buy/sell sides are sorted differently. Think about what happens when a market order needs to sweep through multiple price levels. Compare your approach with the walkthrough below.
Step 1: Identify Core Entities
Start by asking: what are the main "things" in this problem? Look for nouns in your requirements: stock exchange, order book, order, trade, stock, portfolio, user. Each noun is a candidate entity, but not every noun deserves its own class. Group related concepts and assign clear responsibilities.
A common mistake here is putting everything into one giant StockExchange class. That violates SRP immediately. The exchange orchestrates, the order book manages orders for one stock, orders know their own state, and trades are immutable records of what happened.
| Entity | Responsibility | Key attributes |
|---|---|---|
| StockExchange | Top-level orchestrator. Routes orders to the correct order book by stock symbol. | orderBooks (map by symbol), portfolios, eventBus |
| OrderBook | Manages buy and sell sides for ONE stock. Runs the matching algorithm. | buyOrders, sellOrders, stockSymbol |
| Order | Represents a single order with quantity, price, side, and fill state. | orderId, trader, stock, side, quantity, filledQuantity, status |
| LimitOrder | Order with a specific price. Rests in the book if unmatched. | price (limit price) |
| MarketOrder | Order that executes immediately at best available price. Never rests in book. | (no price, matches at market) |
| Trade | Immutable record of a completed match between a buy and sell order. | tradeId, buyOrder, sellOrder, price, quantity, timestamp |
| Stock | Value object representing a listed stock. | symbol, name |
| Portfolio | Tracks one user's cash balance and stock holdings. | userId, cashBalance, holdings (map of symbol to quantity) |
| User | The trader who places orders. | userId, name, portfolio |
| OrderSide | Enum: BUY or SELL | |
| OrderStatus | Enum: OPEN, PARTIALLY_FILLED, FILLED, CANCELLED |
Notice that Order is the base with LimitOrder and MarketOrder as subtypes. They share side, quantity, and status, but differ in matching behavior: limit orders have a price constraint, market orders do not. This is a natural inheritance hierarchy, not forced polymorphism.
Step 2: Define Relationships and Class Design
StockExchange
The top-level orchestrator. It routes orders to the correct order book, validates portfolios, and publishes trade events. It does NOT run the matching algorithm; that is the order book's job.
Deriving state from requirements:
| Requirement | What StockExchange must track |
|---|---|
| "Each stock has its own order book" | A map from stock symbol to OrderBook |
| "Track user portfolios" | A map from user ID to Portfolio |
| "Fire events for trade execution" | An EventBus for publishing trade events |
Deriving methods from needs:
| Need from requirements | Method |
|---|---|
| "Submit limit/market orders" | submitOrder(Order): List<Trade> |
| "Cancel open orders" | cancelOrder(String orderId): boolean |
| "Track portfolios" | getPortfolio(String userId): Portfolio |
OrderBook
The heart of the system. It owns the buy and sell sides and runs matching when a new order arrives. Buy orders are sorted by price descending (highest bid first), sell orders by price ascending (lowest ask first). Within each price level, orders are queued in FIFO order.
Deriving state from requirements:
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.