21. Stock Brokerage System Advanced (Concurrency & Consistency)

A. Realistic Interview Prompt

"Design a stock brokerage system. Users should be able to create an account, deposit and withdraw cash, place buy and sell orders for stocks, and view their portfolio and order history. Assume stock prices come from an external market data feed."

Note what is not specified: whether only market orders are supported or limit orders too, whether an unfilled order sits pending or is rejected outright, and whether execution means "trade against the live feed price" or "match against other users' orders" (a full exchange). A strong candidate surfaces these before writing a single class — the last one in particular determines whether this is an LLD problem at all, or secretly an HLD/distributed-systems one.

B. Difficulty

Advanced, escalating to Advanced + Concurrency/Consistency once the interviewer asks about two orders racing on the same account's cash or holdings (an almost-guaranteed follow-up). It sits a notch above Parking-Lot-style problems because the domain has hard financial invariants — a balance must never go negative, a sell must never exceed held shares — that have to survive concurrent access, not just single-threaded correctness.

C. What This Problem Tests

D. Requirements

Functional

  1. Create an account with a cash balance
  2. Deposit and withdraw cash from an account
  3. Place a buy or sell order for a stock, as a market or limit order
  4. Validate an order against available funds (buy) or available holdings (sell) before accepting it
  5. Execute an order against the current market price — immediately for market orders, or once the price condition is met for limit orders
  6. Maintain a portfolio of holdings per account
  7. Maintain an order/transaction history per account
  8. Allow cancelling an order that hasn't executed yet
  9. Look up the current price of a stock via a market data feed

Non-Functional

E. Clarifying Questions

#QuestionWhy It MattersDesign Decision Affected
1Are both market and limit orders in scope, or market only?Determines whether execution logic needs to be pluggable at allExecutionStrategy abstraction vs. one inline calculation
2If a limit order's price isn't currently met, does it sit pending or get rejected outright?Pending orders need a lifecycle and a way to be re-evaluated laterOrder status machine, feed subscription
3Do orders execute against a live price feed, or get matched against other users' orders?The former is a brokerage (LLD-scale); the latter is a real exchange matching engine (HLD/distributed)Whether an order book/matching engine is even built
4Should funds/holdings be reserved at order-placement time, or only checked at execution time?Without a reservation, two pending orders could both "pass" a check against the same unspent balanceHold-then-settle model on Account/Portfolio
5Can a user place multiple concurrent orders, including on the same stock?The single biggest driver of the concurrency designLocking granularity, atomicity of reserve/settle
6Is a single currency assumed?Avoids an unrequested currency-conversion layerMoney modeling simplicity
7Can a placed order be cancelled?Adds a CANCELLED terminal state and a release-reservation pathOrder lifecycle, cancelOrder()
8Are fractional shares allowed?Changes Quantity from an integer to a decimal typeOrder/Portfolio quantity type
9Are price updates pushed to the system, or does it need to poll?Push enables an efficient Observer-based re-check of pending limit orders; poll wastes cycles and adds latencyMarketDataFeed interface shape
10Must account/order state persist across restarts?Affects ID durability and crash-recovery of in-flight reservationsPersistence section relevance
11Do we need commission/fees per trade?If yes, pricing needs a second pluggable piece separate from executionOptional CommissionStrategy (follow-up, not baked in)

F. Assumptions

G. Actors

ActorResponsibility
Investor / UserOwns an account; deposits/withdraws cash; places and cancels orders
Market Data ProviderExternal source of current stock prices — feeds the system, isn't a human actor in the flow
SystemValidates orders, reserves funds/holdings, executes against the feed price, records history — no human decision in the core flow

H. Core Domain Entities

ClassResponsibilityKey AttributesKey MethodsDoes NOT Handle
AccountHolds cash and a portfolio for one investorid, cashBalance, reservedCashreserve(), release(), deposit(), withdraw()Order validation rules, pricing
PortfolioTracks per-stock holdings for one accountholdings map, reserved-quantity mapreserveShares(), releaseShares(), settle()Pricing, order lifecycle
StockRepresents a tradable instrument and its last known pricesymbol, currentPriceupdatePrice()Deciding who trades it
Order (abstract)Represents one order's lifecycleid, quantity, status, timestampscancel()Fund/holding math, execution decision
BuyOrder/SellOrderConcrete order — fixes direction and which side (funds vs. holdings) gets reserved/settled——Any behavior beyond direction
ExecutionStrategy (interface)Decides whether an order executes at a given price—tryExecute(Order, Price)Reserving/settling funds itself
MarketDataFeed (interface)Supplies current prices and notifies subscribers on change—getPrice(), subscribe()Order logic, account state
Broker (facade)Public entry pointaccountsplaceOrder(), cancelOrder(), deposit(), withdraw()Execution/validation logic itself (delegates)

Deliberately not created (yet): a peer order-matching engine/order book, a PaymentGateway for real money movement, a multi-currency FxConverter, a ComplianceEngine. The reference GitHub write-up this problem is commonly modeled after (ashishps1/awesome-low-level-design) keeps scope similarly narrow, but wires its central class as a Singleton — this handbook deliberately avoids that (see M) for the same reason it avoids Singleton everywhere else: it isn't a class-design requirement, it's an application-wiring decision, and baking it in makes the class harder to test.

I. Class Relationships

J. Class Diagram

Broker

+placeOrder(Account, Stock, Side, Quantity, OrderType, LimitPrice)

+cancelOrder(OrderId)

+deposit(Account, Money)

+withdraw(Account, Money)

Account

id

cashBalance

reservedCash

+reserve(Money)

+release(Money)

+deposit(Money)

+withdraw(Money)

Portfolio

+reserveShares(Stock, Quantity)

+releaseShares(Stock, Quantity)

+settle(Stock, Quantity, Side)

«interface»

ExecutionStrategy

+tryExecute(Order, Price)

«interface»

MarketDataFeed

+getPrice(Symbol)

+subscribe(Symbol, PriceListener)

«abstract»

Order

id

quantity

status

+cancel()

Stock

symbol

currentPrice

MarketExecutionStrategy

LimitExecutionStrategy

BuyOrder

SellOrder

View Mermaid source
classDiagram
  class Broker {
    +placeOrder(Account, Stock, Side, Quantity, OrderType, LimitPrice)
    +cancelOrder(OrderId)
    +deposit(Account, Money)
    +withdraw(Account, Money)
  }
  class Account {
    id
    cashBalance
    reservedCash
    +reserve(Money)
    +release(Money)
    +deposit(Money)
    +withdraw(Money)
  }
  class Portfolio {
    +reserveShares(Stock, Quantity)
    +releaseShares(Stock, Quantity)
    +settle(Stock, Quantity, Side)
  }
  class ExecutionStrategy {
    <<interface>>
    +tryExecute(Order, Price)
  }
  class MarketDataFeed {
    <<interface>>
    +getPrice(Symbol)
    +subscribe(Symbol, PriceListener)
  }
  class Order {
    <<abstract>>
    id
    quantity
    status
    +cancel()
  }
  class Stock {
    symbol
    currentPrice
  }
  Broker *-- Account
  Account *-- Portfolio
  Broker ..> ExecutionStrategy
  Broker ..> MarketDataFeed
  ExecutionStrategy <|.. MarketExecutionStrategy
  ExecutionStrategy <|.. LimitExecutionStrategy
  Order <|-- BuyOrder
  Order <|-- SellOrder
  Order --> Account
  Order --> Stock

K. Responsibility Mapping

ClassResponsibilityDoes NOT Handle
BrokerCoordinates order placement/cancellation, owns accountsExecution decision, reservation math, price sourcing
AccountOwns cash balance and reservation bookkeepingDeciding whether an order can execute
PortfolioOwns holdings and share-reservation bookkeepingPricing, order lifecycle
ExecutionStrategyDecides if/at what price an order executes right nowMutating account or portfolio state directly
MarketDataFeedSupplies prices, notifies subscribers on changeAny order or account logic
OrderRecords one order's facts and statusAny computation — it's a data holder with a status machine

L. SOLID Analysis

PrincipleHow It's AppliedNaive Violation Example
SRPBroker only coordinates; reservation lives on Account/Portfolio, execution logic lives in ExecutionStrategyOne Broker class with checkFunds(), checkPrice(), executeTrade(), updatePortfolio(), logHistory() all inline
OCPNew order type (stop-loss) = new ExecutionStrategy; zero edits to Broker or Orderif (order.type == "LIMIT") ... else if (order.type == "STOP_LOSS") ... chains that grow with every new type
LSPAny Order subtype can be passed through placeOrder()/cancelOrder() without special-casingA subclass that throws "unsupported" for cancel(), breaking the base contract
ISPExecutionStrategy and MarketDataFeed are separate, narrow interfaces — no implementer is forced to implement methods it doesn't needOne fat TradingEngine interface with pricing + execution + notification + persistence methods
DIPBroker depends on the ExecutionStrategy/MarketDataFeed abstractions, injected at construction — not concrete implementationsBroker directly instantiates new LiveMarketDataFeed() internally, making it untestable without a real feed

M. Design Patterns

PatternProblem It SolvesWhere It AppearsAlternativeWhen NOT to Use
StrategyMarket vs. limit (vs. future stop-loss) execution logic varies and grows over timeExecutionStrategyIf only market orders existed: inline a single "execute at current price" methodIf there's exactly one, permanent execution rule — don't add an interface "just in case"
ObserverPending limit orders need to react to price changes without pollingMarketDataFeed.subscribe() notifying pending ordersPoll the feed on a timer for every pending orderIf price updates are rare/manual — polling a tiny data set occasionally is fine and simpler
FacadeCallers shouldn't need to know about reservation, execution strategy selection, and feed wiringBroker.placeOrder()/cancelOrder()Expose Account/Portfolio/ExecutionStrategy directly (leaky abstraction)If the system is trivially small — a facade adds a layer of indirection
State (light)Order lifecycle: PENDING → EXECUTED / REJECTED / CANCELLEDOrder.statusPlain enum + guarded transitions (fine at this scale)Don't build a full State-pattern class hierarchy for 4 states — an enum with guard checks is enough until per-state behavior gets complex

Explicitly avoided: Singleton for Broker. The reference implementation this problem is commonly based on defaults to a Singleton StockBroker "because there's only one broker in the process" — but that's an application-wiring decision (construct one instance, inject it everywhere), not a class-design constraint. Singleton would make it impossible to spin up two independent brokers side-by-side in a unit test, for no real production benefit.

N. What Is Likely to Change?

BehaviorCurrent DesignAnticipated ChangeProtecting Abstraction
Execution ruleMarket (immediate) or limit (price-crossing)Stop-loss, stop-limit, trailing-stop ordersExecutionStrategy interface
Price sourceSingle MarketDataFeedBest-price-across-multiple-exchanges aggregationMarketDataFeed interface
Cost modelNo commissionPer-trade commission, tiered fee schedulesFuture CommissionStrategy interface (not built prematurely)
Account structureOne portfolio per accountMultiple sub-accounts per user (e.g. individual + retirement)User *-- Account[1..*] composition (not built prematurely)
NotificationNot in scopeAlert user on order fill via push/emailFuture Observer/event bus on Order status changes

O. Design Evolution

What stays stable across all versions: the Broker.placeOrder()/cancelOrder() facade signature. That stability is the payoff of extracting ExecutionStrategy and MarketDataFeed early — callers never notice the internal evolution.

P. API / Interface Design

class Broker {
  Order placeOrder(Account account, Stock stock, Side side,
                    int quantity, OrderType type, Money limitPrice);
  // throws InsufficientFundsException, InsufficientHoldingsException, InvalidOrderException

  void cancelOrder(OrderId orderId);
  // throws OrderNotFoundException, InvalidOrderStateException

  void deposit(Account account, Money amount);
  void withdraw(Account account, Money amount);        // throws InsufficientFundsException
}

interface ExecutionStrategy {
  Optional<Money> tryExecute(Order order, Money currentPrice);
}

interface MarketDataFeed {
  Money getPrice(Symbol symbol);
  void  subscribe(Symbol symbol, PriceListener listener);
}

class Account {
  void reserve(Money amount);    // throws InsufficientFundsException
  void release(Money amount);
  void deposit(Money amount);
  void withdraw(Money amount);   // throws InsufficientFundsException
}

Ownership/validation notes: placeOrder owns the reserve-then-attempt-execute atomicity — a caller never sees a half-reserved order. cancelOrder must validate the order is still PENDING before cancelling, and must release any funds/shares it reserved — this is the boundary where "cancel after it already executed" bugs occur if skipped.

Q. Pseudocode — Critical Logic

placeOrder(account, stock, side, qty, type, limitPrice):
  if side == BUY:
    estimatedCost = qty * (type == MARKET ? feed.getPrice(stock) : limitPrice)
    account.reserve(estimatedCost)              // throws InsufficientFundsException
  else:
    account.portfolio.reserveShares(stock, qty)  // throws InsufficientHoldingsException

  order = Order.create(account, stock, side, qty, type, limitPrice)
  strategy = strategyFor(type, limitPrice)
  price = feed.getPrice(stock)
  result = strategy.tryExecute(order, price)     // must be atomic w.r.t. account state — see W

  if result is present:
    settle(order, result)
  else:
    order.status = PENDING
    feed.subscribe(stock, order)                 // re-checked on future price ticks
  return order

onPriceUpdate(stock, newPrice):                   // Observer callback, not polling
  for order in pendingOrdersFor(stock):
    result = order.strategy.tryExecute(order, newPrice)
    if result is present: settle(order, result)

settle(order, executionPrice):
  if order.side == BUY:
    account.release(order.reservedEstimate)
    account.withdraw(executionPrice * order.quantity)
    portfolio.addHolding(order.stock, order.quantity)
  else:
    portfolio.settle(order.stock, order.quantity, SELL)   // removes reserved shares
    account.deposit(executionPrice * order.quantity)
  order.status = EXECUTED

cancelOrder(orderId):
  order = orderRepository.find(orderId)
  if order.status != PENDING: raise InvalidOrderStateException
  releaseReservation(order)                        // cash or shares, by side
  order.status = CANCELLED

R. End-to-End Use Case

Investor
  │  places a limit buy order
  ▼
Broker.placeOrder(account, stock, BUY, qty, LIMIT, limitPrice)
  │
  ▼
Account.reserve(estimatedCost)        // funds held, not yet spent
  │
  ▼
LimitExecutionStrategy.tryExecute(order, feed.getPrice(stock))
  │  price hasn't crossed the limit yet → returns empty
  ▼
Order.status = PENDING
  │
  ▼
MarketDataFeed.subscribe(stock, order)
  ...time passes, price ticks arrive...
MarketDataFeed → onPriceUpdate(stock, newPrice)
  │
  ▼
LimitExecutionStrategy.tryExecute(order, newPrice)   // now crosses the limit
  │
  ▼
settle(order, executionPrice)
  │  Account.withdraw(), Portfolio.addHolding()
  ▼
Order.status = EXECUTED                 // investor sees the fill in history

S. Data Structures

StructureUsed ForWhyAlternative & Trade-off
Map<OrderId, Order>Order lookup for cancel/status checksO(1) lookup by order IDDB-backed lookup if persistence is required (see X)
Map<Symbol, List<Order>>Pending limit orders subscribed per stockO(k) re-check on a price tick, where k = pending orders for that stock only — not all ordersA single global pending-order list — simpler, but forces scanning irrelevant orders on every tick
Integer cents (not float) for MoneyAll cash and price arithmeticAvoids floating-point rounding errors compounding across tradesArbitrary-precision decimal type — safer at odd fractions, slightly more overhead than plain integers

T. Complexity Analysis

OperationTimeSpace
placeOrder (market, immediate execution)O(1)O(1) extra per order
placeOrder (limit, goes pending)O(1) to enqueue for the subscribed stockO(1) per pending order
onPriceUpdate re-checkO(k), k = pending orders for that one stockO(1) extra
cancelOrderO(1) with an order-ID mapO(1)
Overall order/account storage—O(accounts + orders)

U. Testing Strategy

V. Interviewer Follow-Ups

Follow-UpTestingDesign ChangeClasses ChangedClasses UnchangedPattern/Trade-off
"Add stop-loss orders."OCP under a new requirementNew execution rule triggered by price falling below a thresholdNew StopLossExecutionStrategy onlyBroker, OrderValidates the Strategy investment made in V3
"Two sell orders for the same stock race, but holdings only cover one."Concurrency / race conditionsShare reservation on Portfolio must be atomicPortfolio.reserveShares()External API signatureOptimistic (CAS) vs. pessimistic (lock) per account — see W
"Add a commission per trade."Recognizing a second, independent variability axisNew pluggable fee calculation consulted during settle()New CommissionStrategy onlyOrder, Broker APIDon't fold this into ExecutionStrategy — pricing and fees vary independently
"Support real matching against other users' orders."LLD → HLD boundary awarenessThis crosses into an exchange matching engine / order book designNone in-process — this is a new subsystemAll core classes — the account/order model doesn't change, only how execution is sourcedRecognize the boundary rather than trying to build a matching engine inline
"The price feed goes down mid-trade."Failure handlingBroker must reject/hold rather than execute against a stale priceMarketDataFeed gains a staleness/health checkOrder, AccountFail closed, not silently trade on old data
"A cancel request arrives just as the order is executing."Concurrency / race conditionsCancel and execute must both go through an atomic status transition on the orderOrder.status transition (CAS)External API signatureWhichever wins the CAS is final — no double-outcome
"Support margin / short-selling."Business-rule modelingAccount gains a borrowing limit; Portfolio allows constrained negative holdingsAccount, Portfolio validation logicBroker, Order APIPush the new rule into validation, not into the allocator/execution path
"Millions of users trading simultaneously."LLD → HLD boundary awarenessSharding accounts by user ID, a message bus fanning out price ticks, a database instead of in-memory mapsNone in-processAll core classes — the object model doesn't change, only where it runsRecognize the boundary rather than trying to distributed-system-ify the domain classes
"How would you test this without a real market feed?"Testability awarenessInject a fake MarketDataFeed with a scriptable price sequenceNone — this is exactly why MarketDataFeed is an interface—Dependency injection over a hardcoded live feed call

W. Concurrency

Shared mutable state: an account's cashBalance/reservedCash and a portfolio's holdings/reserved-quantity. Two orders racing to spend or sell against the same account is the canonical hazard here.

X. Persistence

If state must survive a restart: Account, Portfolio, and Order become persistent entities with durable IDs, not in-memory object references. An AccountRepository/OrderRepository abstraction replaces direct in-memory maps — same interface shape, different backing store. The reserve-then-settle step becomes a database transaction (or an optimistic-locking update with a version column), so a crash between "reserve" and "settle" can't leave cash permanently held or shares double-counted. Order IDs should be generated in a way that's safe across restarts (UUID, not an in-memory counter).

Y. Scaling Boundary

LayerWhat Belongs Here
LLD (this document)Object relationships, responsibilities, APIs, in-process state, patterns for one brokerage instance
HLDA BrokerService behind a load balancer, a database sharded by account/user ID, a message bus fanning out price ticks to interested pending orders, a cache for "current price" reads
Distributed SystemsOnly relevant if building a real exchange: a matching engine/order book that pairs buyers with sellers directly, consensus on trade sequencing, and fairness guarantees across data centers — a fundamentally different problem than a brokerage that trades against a price feed

Z. Failure Scenarios

AA. Common Bad Designs

AB. Design Trade-Offs

DecisionOption AOption BTrade-off
Money representationInteger centsFloating-pointInteger cents is exact but needs care converting for display; float is convenient but silently accumulates rounding errors across many trades
Fund/holding validation timingReserve at order-placement timeCheck only at execution timeReserve-then-settle prevents overselling/overspending under concurrent pending orders, at the cost of extra bookkeeping (release paths on cancel/failure)
Limit-order price re-checkPush (Observer on feed updates)Poll on a timerPush is real-time and efficient but needs pub-sub wiring; poll is simpler to write but wastes cycles and adds latency proportional to the polling interval
Lock granularityOne lock per accountOne global lockPer-account is naturally parallel since accounts are independent; a global lock is simpler to reason about but serializes unrelated users' trades for no reason

AC. Interviewer Evaluation

AD. Interview Simulation

Interviewer: Design a stock brokerage system.
Candidate: Before I start — when I place an order, does it execute against a live market price feed, or does it get matched against other users' orders directly?
Interviewer: Assume it executes against a market data feed — you're not building an exchange.
Candidate: Good, that keeps this in LLD territory. I'll model a MarketDataFeed interface the broker depends on, so the price source is swappable and testable.
Interviewer: We need both market and limit orders, and pricing rules like commissions may come later.
Candidate: Then I'll isolate execution behind an ExecutionStrategy interface — market executes immediately, limit checks the price condition — and keep commission as a separate, future strategy rather than bolting it onto the same interface, since they vary independently.
Interviewer: What stops a user from placing two orders that together overdraw their balance?
Candidate: I'll reserve funds at order-placement time rather than only checking the balance — the reservation itself has to be atomic, guarded by a per-account lock, so two concurrent orders on the same account can't both pass a check against the same unspent cash.
Interviewer: The price feed goes down for thirty seconds. What happens to pending limit orders?
Candidate: They should just stay pending — I wouldn't execute against a stale or missing price. I'd add a staleness check on the feed and fail closed rather than trade on old data.
Interviewer: Last one — how would this look for millions of users trading at once?
Candidate: That crosses from LLD into HLD — I'd keep this exact account/order model, but shard accounts by user ID, put a message bus in front of price-tick fan-out instead of in-process subscriptions, and back the repositories with a real database. The object design here doesn't need to change; only where it runs does.

AE. Online Solution Comparison

SourceApproachStrengthWeaknessWorth Learning
ashishps1/awesome-low-level-design (GitHub)Abstract Order with BuyOrder/SellOrder, an OrderStatus enum, a Portfolio for holdings, and a Singleton StockBroker as the central coordinatorClean, minimal entity set that maps closely to the real domain; good baseline vocabulary (Order/Account/Portfolio/Stock)Singleton on the central coordinator is a questionable default (see M); execution is a same-thread call with no explicit reservation step, so it doesn't address the double-spend race this document calls out in WThe core entity shape (Order/Account/Portfolio/Stock) — adopted and then hardened with reservation semantics and a pluggable execution strategy in this document

This problem is less standardized across public LLD write-ups than something like Parking Lot — most tutorial-style sources converge on a similar entity shape (Account/Order/Portfolio) but rarely address the concurrent-reservation problem directly, which is why section W here goes further than most references do.

AF. Interview Cheat Sheet

ProblemStock Brokerage System
DifficultyAdvanced (Concurrency & Consistency)
Core requirementsAccount with cash + portfolio; buy/sell orders (market/limit); execution against a price feed; order/history tracking
Key clarificationsFeed-execution or real order matching? Reserve-at-placement or check-at-execution? Limit orders pending or rejected?
Core entitiesAccount, Portfolio, Stock, Order (Buy/Sell), ExecutionStrategy, MarketDataFeed, Broker (facade)
Key relationshipsComposition: Broker→Account→Portfolio. Dependency: Broker→ExecutionStrategy/MarketDataFeed (interfaces). Observer: feed→pending orders
Key patternsStrategy (execution), Observer (price updates), Facade (Broker)
Most important decisionReserving funds/holdings at order-placement time, not just checking a balance — this is what prevents concurrent double-spend
Likely-changing behaviorNew order types, commission model, multi-account ownership
Concurrency concernAtomic reserve-then-settle per account under concurrent orders
Top follow-upsStop-loss orders, cancel/execute race, feed outage, margin/short-selling, city-scale users (HLD boundary)
Common mistakesGod-class Broker; Singleton by default; float for money; polling instead of Observer
What NOT to over-engineerA real order-matching engine, multi-currency, tax reporting — unless explicitly asked

AG. Full Implementation

A complete, compilable reference implementation of every class in the diagram above (composition, inheritance, interface realization, and dependency-injected strategies all preserved per section I), with a runnable demo exercising the primary flow — in Java, C++, JavaScript, and TypeScript.

// Problem 21: Stock Brokerage System
// Patterns: Strategy (ExecutionStrategy), Observer (MarketDataFeed -> Broker), Facade (Broker)
// Concurrency: per-account lock guards reserve+execute+settle so cash/holdings never corrupt
// under concurrent orders (badge: Advanced, Concurrency & Consistency).
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

public class p21 {

    // ---------- Exceptions ----------
    static class InsufficientFundsException extends RuntimeException { InsufficientFundsException(String m) { super(m); } }
    static class InsufficientHoldingsException extends RuntimeException { InsufficientHoldingsException(String m) { super(m); } }
    static class InvalidOrderStateException extends RuntimeException { InvalidOrderStateException(String m) { super(m); } }
    static class OrderNotFoundException extends RuntimeException { OrderNotFoundException(String m) { super(m); } }

    enum Side { BUY, SELL }
    enum OrderType { MARKET, LIMIT }
    enum OrderStatus { PENDING, EXECUTED, CANCELLED }

    // ---------- Stock ----------
    static class Stock {
        final String symbol;
        volatile double currentPrice;
        Stock(String symbol, double currentPrice) { this.symbol = symbol; this.currentPrice = currentPrice; }
        void updatePrice(double newPrice) { this.currentPrice = newPrice; }
    }

    // ---------- Portfolio (owned by Account) ----------
    static class Portfolio {
        private final Map<String, Integer> holdings = new HashMap<>();
        private final Map<String, Integer> reserved = new HashMap<>();

        void reserveShares(Stock stock, int qty) {
            int owned = holdings.getOrDefault(stock.symbol, 0);
            int alreadyReserved = reserved.getOrDefault(stock.symbol, 0);
            if (owned - alreadyReserved < qty) throw new InsufficientHoldingsException("Not enough shares of " + stock.symbol);
            reserved.merge(stock.symbol, qty, Integer::sum);
        }

        void releaseShares(Stock stock, int qty) {
            reserved.merge(stock.symbol, -qty, Integer::sum);
        }

        void addHolding(Stock stock, int qty) {
            holdings.merge(stock.symbol, qty, Integer::sum);
        }

        // Removes settled (sold) shares from both holdings and their reservation.
        void settle(Stock stock, int qty, Side side) {
            if (side == Side.SELL) {
                holdings.merge(stock.symbol, -qty, Integer::sum);
                reserved.merge(stock.symbol, -qty, Integer::sum);
            }
        }

        int holdingOf(String symbol) { return holdings.getOrDefault(symbol, 0); }
    }

    // ---------- Account (owns Portfolio) ----------
    static class Account {
        final String id;
        double cashBalance;
        double reservedCash;
        final Portfolio portfolio = new Portfolio();
        final ReentrantLock lock = new ReentrantLock(); // guards reserve/execute/settle atomicity

        Account(String id, double initialCash) { this.id = id; this.cashBalance = initialCash; }

        void reserve(double amount) {
            if (cashBalance - reservedCash < amount) throw new InsufficientFundsException("Insufficient funds in " + id);
            reservedCash += amount;
        }
        void release(double amount) { reservedCash = Math.max(0, reservedCash - amount); }
        void deposit(double amount) { cashBalance += amount; }
        void withdraw(double amount) {
            if (cashBalance < amount) throw new InsufficientFundsException("Insufficient funds in " + id);
            cashBalance -= amount;
        }
    }

    // ---------- Order (abstract) ----------
    static abstract class Order {
        final String id;
        final Account account;
        final Stock stock;
        final int quantity;
        final OrderType type;
        final double limitPrice; // unused for MARKET
        double reservedEstimate;
        OrderStatus status = OrderStatus.PENDING;
        final long createdAt = System.nanoTime();

        Order(String id, Account account, Stock stock, int quantity, OrderType type, double limitPrice) {
            this.id = id; this.account = account; this.stock = stock;
            this.quantity = quantity; this.type = type; this.limitPrice = limitPrice;
        }

        abstract Side getSide();

        void cancel() {
            if (status != OrderStatus.PENDING) throw new InvalidOrderStateException("Order not cancellable: " + status);
            status = OrderStatus.CANCELLED;
        }
    }

    static class BuyOrder extends Order {
        BuyOrder(String id, Account account, Stock stock, int quantity, OrderType type, double limitPrice) {
            super(id, account, stock, quantity, type, limitPrice);
        }
        @Override Side getSide() { return Side.BUY; }
    }

    static class SellOrder extends Order {
        SellOrder(String id, Account account, Stock stock, int quantity, OrderType type, double limitPrice) {
            super(id, account, stock, quantity, type, limitPrice);
        }
        @Override Side getSide() { return Side.SELL; }
    }

    // ---------- ExecutionStrategy ----------
    interface ExecutionStrategy {
        OptionalDouble tryExecute(Order order, double currentPrice);
    }

    static class MarketExecutionStrategy implements ExecutionStrategy {
        @Override public OptionalDouble tryExecute(Order order, double currentPrice) {
            return OptionalDouble.of(currentPrice); // market orders always execute immediately
        }
    }

    static class LimitExecutionStrategy implements ExecutionStrategy {
        @Override public OptionalDouble tryExecute(Order order, double currentPrice) {
            boolean conditionMet = order.getSide() == Side.BUY
                    ? currentPrice <= order.limitPrice
                    : currentPrice >= order.limitPrice;
            return conditionMet ? OptionalDouble.of(currentPrice) : OptionalDouble.empty();
        }
    }

    // ---------- MarketDataFeed ----------
    interface PriceListener {
        void onPriceUpdate(String symbol, double newPrice);
    }

    interface MarketDataFeed {
        double getPrice(String symbol);
        void subscribe(String symbol, PriceListener listener);
    }

    static class SimpleMarketDataFeed implements MarketDataFeed {
        private final Map<String, Stock> stocks;
        private final Map<String, List<PriceListener>> listeners = new HashMap<>();

        SimpleMarketDataFeed(Map<String, Stock> stocks) { this.stocks = stocks; }

        @Override public double getPrice(String symbol) { return stocks.get(symbol).currentPrice; }

        @Override public void subscribe(String symbol, PriceListener listener) {
            List<PriceListener> ls = listeners.computeIfAbsent(symbol, k -> new ArrayList<>());
            if (!ls.contains(listener)) ls.add(listener);
        }

        void updatePrice(String symbol, double newPrice) {
            stocks.get(symbol).updatePrice(newPrice);
            for (PriceListener l : listeners.getOrDefault(symbol, Collections.emptyList())) {
                l.onPriceUpdate(symbol, newPrice); // push, don't poll
            }
        }
    }

    // ---------- Broker (Facade) ----------
    static class Broker implements PriceListener {
        private final Map<String, Account> accounts = new HashMap<>();
        private final Map<String, Order> ordersById = new HashMap<>();
        private final Map<String, List<Order>> pendingOrdersBySymbol = new HashMap<>();
        private final MarketDataFeed feed;
        private final ExecutionStrategy marketStrategy = new MarketExecutionStrategy();
        private final ExecutionStrategy limitStrategy = new LimitExecutionStrategy();
        private int nextOrderId = 1;

        Broker(MarketDataFeed feed) { this.feed = feed; }

        void addAccount(Account account) { accounts.put(account.id, account); }

        void deposit(Account account, double amount) { account.deposit(amount); }

        void withdraw(Account account, double amount) { account.withdraw(amount); }

        Order placeOrder(Account account, Stock stock, Side side, int quantity, OrderType type, double limitPrice) {
            account.lock.lock(); // guard reserve + execution-attempt + settle atomically
            try {
                ExecutionStrategy strategy = (type == OrderType.MARKET) ? marketStrategy : limitStrategy;
                double currentPrice = feed.getPrice(stock.symbol);
                String id = "ORD-" + (nextOrderId++);
                Order order;

                if (side == Side.BUY) {
                    double estimatedCost = quantity * (type == OrderType.MARKET ? currentPrice : limitPrice);
                    account.reserve(estimatedCost); // throws InsufficientFundsException
                    order = new BuyOrder(id, account, stock, quantity, type, limitPrice);
                    order.reservedEstimate = estimatedCost;
                } else {
                    account.portfolio.reserveShares(stock, quantity); // throws InsufficientHoldingsException
                    order = new SellOrder(id, account, stock, quantity, type, limitPrice);
                }
                ordersById.put(order.id, order);

                OptionalDouble result = strategy.tryExecute(order, currentPrice);
                if (result.isPresent()) {
                    settle(order, result.getAsDouble());
                } else {
                    order.status = OrderStatus.PENDING;
                    pendingOrdersBySymbol.computeIfAbsent(stock.symbol, k -> new ArrayList<>()).add(order);
                    feed.subscribe(stock.symbol, this); // re-checked on future price ticks
                }
                return order;
            } finally {
                account.lock.unlock();
            }
        }

        @Override public void onPriceUpdate(String symbol, double newPrice) {
            List<Order> pending = pendingOrdersBySymbol.get(symbol);
            if (pending == null) return;
            List<Order> stillPending = new ArrayList<>();
            for (Order order : new ArrayList<>(pending)) {
                if (order.status != OrderStatus.PENDING) continue; // already cancelled/executed
                order.account.lock.lock();
                try {
                    ExecutionStrategy strategy = (order.type == OrderType.MARKET) ? marketStrategy : limitStrategy;
                    OptionalDouble result = strategy.tryExecute(order, newPrice);
                    if (result.isPresent()) {
                        settle(order, result.getAsDouble());
                    } else {
                        stillPending.add(order);
                    }
                } finally {
                    order.account.lock.unlock();
                }
            }
            pendingOrdersBySymbol.put(symbol, stillPending);
        }

        // Must be called while holding order.account.lock.
        private void settle(Order order, double executionPrice) {
            Account account = order.account;
            if (order.getSide() == Side.BUY) {
                account.release(order.reservedEstimate);
                account.withdraw(executionPrice * order.quantity);
                account.portfolio.addHolding(order.stock, order.quantity);
            } else {
                account.portfolio.settle(order.stock, order.quantity, Side.SELL); // removes reserved shares
                account.deposit(executionPrice * order.quantity);
            }
            order.status = OrderStatus.EXECUTED;
        }

        void cancelOrder(String orderId) {
            Order order = ordersById.get(orderId);
            if (order == null) throw new OrderNotFoundException("No such order: " + orderId);
            order.account.lock.lock();
            try {
                if (order.status != OrderStatus.PENDING) throw new InvalidOrderStateException("Order not cancellable: " + order.status);
                if (order.getSide() == Side.BUY) order.account.release(order.reservedEstimate);
                else order.account.portfolio.releaseShares(order.stock, order.quantity);
                order.status = OrderStatus.CANCELLED;
            } finally {
                order.account.lock.unlock();
            }
        }
    }

    // ---------- Demo ----------
    public static void main(String[] args) throws InterruptedException {
        Stock appl = new Stock("AAPL", 150.0);
        Map<String, Stock> stocks = new HashMap<>();
        stocks.put("AAPL", appl);
        SimpleMarketDataFeed feed = new SimpleMarketDataFeed(stocks);

        Broker broker = new Broker(feed);
        Account account = new Account("A1", 10000.0);
        broker.addAccount(account);

        // Market buy: executes immediately.
        Order buy = broker.placeOrder(account, appl, Side.BUY, 10, OrderType.MARKET, 0);
        System.out.println("Market buy status=" + buy.status + " cash=" + account.cashBalance + " holdings=" + account.portfolio.holdingOf("AAPL"));

        // Limit sell above current price: stays pending until price rises.
        Order limitSell = broker.placeOrder(account, appl, Side.SELL, 5, OrderType.LIMIT, 160.0);
        System.out.println("Limit sell status=" + limitSell.status);

        feed.updatePrice("AAPL", 161.0); // Observer push triggers re-check
        System.out.println("After price tick, limit sell status=" + limitSell.status + " cash=" + account.cashBalance);

        // Place another limit sell, then cancel it before it executes.
        Order toCancel = broker.placeOrder(account, appl, Side.SELL, 2, OrderType.LIMIT, 500.0);
        broker.cancelOrder(toCancel.id);
        System.out.println("Cancelled order status=" + toCancel.status + " holdings=" + account.portfolio.holdingOf("AAPL"));

        // Concurrency demo: two threads race to sell the last 3 shares concurrently.
        Account raceAccount = new Account("A2", 5000.0);
        broker.addAccount(raceAccount);
        broker.placeOrder(raceAccount, appl, Side.BUY, 3, OrderType.MARKET, 0); // gives raceAccount exactly 3 shares

        Runnable trySell = () -> {
            try {
                broker.placeOrder(raceAccount, appl, Side.SELL, 3, OrderType.MARKET, 0);
            } catch (InsufficientHoldingsException e) {
                System.out.println("Thread correctly rejected: " + e.getMessage());
            }
        };
        Thread t1 = new Thread(trySell);
        Thread t2 = new Thread(trySell);
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println("Race account final holdings=" + raceAccount.portfolio.holdingOf("AAPL") + " (never negative)");
    }
}