Machine Coding Problem

Stock Brokerage System

maco60macoAllfintechcommand-patternorder-matching-enginepriority-queuesobserver-patternthread-safe-order-books
Commonly Asked By:RobinhoodCoinbaseGoldman Sachs

Requirements & System Scope

Functional Scope (In-Scope)

  • Sorted Bid/Ask Order Books: Manage isolated order books per ticker symbol using double sorted priority queues (Price-time priority matching).
  • Price-Time Execution Engine: Match orders deterministically when Bid limit price >= Ask limit price.
  • Command Transactions (Command Pattern): Encapsulate order actions (PLACE, CANCEL, MODIFY) systematically for unified auditing and transaction history.
  • Observer Notifications: Enable observers to listen to matched trade executions and notify users dynamically.

Explicit Boundaries (Out-of-Scope)

  • No Real Exchange Connectivity (FIX Protocol): Bypasses direct network socket links to NASDAQ, NYSE, or external clearing firms.
  • No Advanced Margin Lending Algorithms: Bypasses complex margin interest rates or automatic collateral liquidation sweeps.

Class Diagram & Entity Relationships

Structural layout showing relationships between order commands, books, matching engines, and observers:

Loading...
  • Atomic Share Orders: Order keeps track of purchase side (BUY/SELL), price constraints, and timestamps.
  • Command Pattern Actions: PlaceOrderCommand handles audit executions uniformly.

Design Patterns & SOLID Principles

  • Command Design Pattern (Trading Actions): Decoupling trading transactions under command interfaces protects matching engines from race conditions and gives simple audit/undo abilities.
  • Observer Design Pattern (Trade Executions): Broadcasts matched trades instantly to notifications systems or historical databases without nesting cross-cutting log logics inside matching queues.
  • Single Responsibility Principle (SRP):OrderBook holds lists; MatchingEngine crosses bids; Portfolio tracks assets.

Core Execution Workflows

Limit Order Matching Sequence

  1. Rider calls limit transaction: addOrder(new Order(BUY, "AAPL", 100, 150.00)).
  2. Insert order into bids priority queue (sorted descending by price).
  3. Trigger matching sweep: matchOrders().
  4. Compare top bid (150.00) with top ask (e.g. 149.50):
    1. Since bid >= ask, execute match at counterparty price (149.50).
    2. Deduct filled quantities from both orders.
    3. Broadcast trade executions to observers.
    4. If either order is completely filled, remove it from queue.
  5. Repeat scan until prices no longer overlap.

Concurrency & Thread Safety Strategy

When multiple threads try to place, cross, and cancel orders on the same ticker book simultaneously:

  • Per-Symbol Matching Locks: Lock matching operations per symbol instead of locking the entire system to prevent bottlenecking.
  • Thread-Safe Queues: Wrap standard heaps inside synchronised decorators to avoid corrupted array index heaps.

Complete Clean Code Blueprint

Clean reference designs demonstrating order-matching engines in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

enum OrderType { MARKET, LIMIT }
enum OrderSide { BUY, SELL }

class Order {
    private final String id;
    private final String userId;
    private final String symbol;
    private final OrderType type;
    private final OrderSide side;
    private int quantity;
    private final double limitPrice;
    private final long timestamp;

    public Order(String id, String userId, String symbol, OrderType type, OrderSide side, int quantity, double limitPrice) {
        this.id = id;
        this.userId = userId;
        this.symbol = symbol;
        this.type = type;
        this.side = side;
        this.quantity = quantity;
        this.limitPrice = limitPrice;
        this.timestamp = System.nanoTime();
    }

    public String getId() { return id; }
    public String getUserId() { return userId; }
    public String getSymbol() { return symbol; }
    public OrderType getType() { return type; }
    public OrderSide getSide() { return side; }
    public int getQuantity() { return quantity; }
    public double getLimitPrice() { return limitPrice; }
    public long getTimestamp() { return timestamp; }

    public void deductQuantity(int qty) { this.quantity -= qty; }
    public boolean isFilled() { return quantity == 0; }
}

interface TradeObserver {
    void onTradeExecuted(Order buyOrder, Order sellOrder, int quantity, double price);
}

class TransactionLogger implements TradeObserver {
    @Override
    public void onTradeExecuted(Order buyOrder, Order sellOrder, int quantity, double price) {
        System.out.println(String.format("[Trade Executed] %d shares of %s matched between User %s and User %s at $%.2f", 
                quantity, buyOrder.getSymbol(), buyOrder.getUserId(), sellOrder.getUserId(), price));
    }
}

// โ”€โ”€โ”€ COMMAND PATTERN (TRADING TRANSACTION) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
interface OrderCommand {
    boolean execute();
    void undo();
}

class PlaceOrderCommand implements OrderCommand {
    private final BrokerageSystem system;
    private final Order order;

    public PlaceOrderCommand(BrokerageSystem system, Order order) {
        this.system = system;
        this.order = order;
    }

    @Override
    public boolean execute() {
        system.placeOrder(order);
        return true;
    }

    @Override
    public void undo() {
        system.cancelOrder(order.getSymbol(), order.getId());
    }
}

class OrderBook {
    private final String symbol;
    // Bids sorted descending by limit price. For matching timestamps, earlier order has priority.
    private final PriorityQueue<Order> bids = new PriorityQueue<>((o1, o2) -> {
        int priceCompare = Double.compare(o2.getLimitPrice(), o1.getLimitPrice());
        if (priceCompare != 0) return priceCompare;
        return Long.compare(o1.getTimestamp(), o2.getTimestamp());
    });
    // Asks sorted ascending by limit price. For matching timestamps, earlier order has priority.
    private final PriorityQueue<Order> asks = new PriorityQueue<>((o1, o2) -> {
        int priceCompare = Double.compare(o1.getLimitPrice(), o2.getLimitPrice());
        if (priceCompare != 0) return priceCompare;
        return Long.compare(o1.getTimestamp(), o2.getTimestamp());
    });

    private final ReentrantLock bookLock = new ReentrantLock();
    private final List<TradeObserver> observers = new CopyOnWriteArrayList<>();

    public OrderBook(String symbol) {
        this.symbol = symbol;
    }

    public void addObserver(TradeObserver obs) { observers.add(obs); }

    public void addOrder(Order order) {
        bookLock.lock();
        try {
            if (order.getSide() == OrderSide.BUY) {
                bids.add(order);
            } else {
                asks.add(order);
            }
            matchOrders();
        } finally {
            bookLock.unlock();
        }
    }

    public void cancelOrder(String orderId) {
        bookLock.lock();
        try {
            bids.removeIf(o -> o.getId().equals(orderId));
            asks.removeIf(o -> o.getId().equals(orderId));
        } finally {
            bookLock.unlock();
        }
    }

    private void matchOrders() {
        while (!bids.isEmpty() && !asks.isEmpty()) {
            Order bid = bids.peek();
            Order ask = asks.peek();

            // Match limit overlapping pricing
            if (bid.getLimitPrice() >= ask.getLimitPrice()) {
                int matchQty = Math.min(bid.getQuantity(), ask.getQuantity());
                double executionPrice = ask.getLimitPrice(); // Price of standing order

                bid.deductQuantity(matchQty);
                ask.deductQuantity(matchQty);

                for (TradeObserver obs : observers) {
                    obs.onTradeExecuted(bid, ask, matchQty, executionPrice);
                }

                if (bid.isFilled()) bids.poll();
                if (ask.isFilled()) asks.poll();
            } else {
                break;
            }
        }
    }
}

class BrokerageSystem {
    private final Map<String, OrderBook> orderBooks = new ConcurrentHashMap<>();
    private final TransactionLogger logger = new TransactionLogger();

    public void placeOrder(Order order) {
        OrderBook book = orderBooks.computeIfAbsent(order.getSymbol(), k -> {
            OrderBook newBook = new OrderBook(k);
            newBook.addObserver(logger);
            return newBook;
        });
        book.addOrder(order);
    }

    public void cancelOrder(String symbol, String orderId) {
        OrderBook book = orderBooks.get(symbol);
        if (book != null) {
            book.cancelOrder(orderId);
            System.out.println("[Service] Order Canceled: " + orderId);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("=== Stock Brokerage Matching Engine ===");
        BrokerageSystem system = new BrokerageSystem();

        // Placing matching limit orders
        System.out.println("\n--- Client places Limit Bid ---");
        Order buyOrder = new Order("ORD-1", "User-A", "AAPL", OrderType.LIMIT, OrderSide.BUY, 50, 150.00);
        OrderCommand cmd1 = new PlaceOrderCommand(system, buyOrder);
        cmd1.execute();

        System.out.println("\n--- Client places overlapping Limit Ask (Causes Match) ---");
        Order sellOrder = new Order("ORD-2", "User-B", "AAPL", OrderType.LIMIT, OrderSide.SELL, 30, 149.50);
        OrderCommand cmd2 = new PlaceOrderCommand(system, sellOrder);
        cmd2.execute();
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...