Back🏗️

Trade Subscription Manager (LLD)

AWAKENING0 / 100
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░]0%

Trade Subscription Manager (LLD)

Low Level Design·advanced·~30 min read

  • lld
  • concurrency
  • market-data
  • design
  • java
  • bloomberg

What you'll learn

  • Problem Statement

    Trading terminals and market-data desks need to distribute a live stream of trades to interested subscribers. A subscriber says "notify me every time a trade happens on AAPL with volume at least 5,000 shares." When a trade prints, every…

  • Step 1 — Understand the Query Patterns Before You Write Anything

    Before drawing classes, get the shape of the load right. This is what separates a design that survives review from one that gets torn apart on the second question. Three method calls, three very different frequencies: | Method | Frequenc…

  • V1 — Naive, No Locks

    Start with the smallest thing that could possibly work. One list of subscriptions, linear scan on every trade. This works — for a single thread, with no concurrent trades, and a small subscriber count. Say so out loud. The interviewer wa…

  • Follow-up 1 — What breaks when two threads call in at once?

    Three concrete failures. Walk through each; don't hand-wave.

  • Failure A — ConcurrentModificationException mid-broadcast

    Thread T1 is inside onTrade, halfway through iterating subs. Thread T2 calls subscribe (or a callback re-enters unsubscribe). The list mutates while T1 iterates. ArrayList's iterator detects the modification count changed and throws.

  • Failure B — Duplicate Handle.id

    nextId++ is not atomic in Java. It compiles to: read nextId into a register, add 1, write back. Two threads reading the same value hand back the same id to two different subscribers. Now their Handles collide — one of them calling unsubs…

  • Failure C — Callback that unsubscribes itself

    A common subscriber pattern: "notify me once, then stop." The callback calls TradeProcessor.unsubscribe(myHandle) from inside onTrade. In V1, that reaches back into subs.removeIf(...) while the outer onTrade is still iterating the same l…

  • The V2 fix

    Guard every read and write with a single lock, and generate the id with an AtomicInteger. Two things worth pointing out to the interviewer: 1. Why the snapshot? If we invoked callbacks while holding the lock, a slow subscriber would stal…

  • Failure C revisited

    Because Java's synchronized is reentrant per thread, the callback-driven unsubscribe in V2 doesn't deadlock. But even better, the snapshot approach means the unsubscribe mutates the real subs list while onTrade iterates the copy. The uns…

  • Follow-up 2 — The Hot Path Scans Everyone

    V2 is correct. But onTrade's snapshot is O(N) in the total number of subscriptions across all symbols. On a broker with 10,000 symbols and 100 subscribers per symbol (1M total), every AAPL trade tick copies and scans a million-element li…

  • V3 — Bucket by Security.name

    What we gained: onTrade for AAPL only scans AAPL subscribers. Cost went from O(totalsubs) to O(subsforthissymbol) — typically 3-4 orders of magnitude smaller. What got a little worse: unsubscribe no longer knows which bucket the Handle l…

  • Follow-up 3 — Within a Symbol, Most Callbacks Still Don't Fire

    On a symbol like AAPL with 1,000 subscribers, a typical trade of size 500 might match only the 50 whose minSize ≤ 500. V3 still iterates all 1,000 to filter. The observation: a subscription with minSize = 10,000 cares about very few trad…

  • V4 — Bucket by minSize within each symbol

    Replace List inside each symbol with a NavigableMap> — a TreeMap keyed by minSize, values are the subscriptions at exactly that threshold. onTrade calls headMap(trade.size, true) to get the sub-view of eligible entries and iterates only…

  • Follow-up 4 — Can We Drop the Single Lock?

    V4 still funnels every onTrade through one synchronized block. On a machine with many cores and many symbols, the lock becomes the bottleneck — two AAPL trades and one GOOG trade all serialize on the same monitor even though they touch d…

  • Follow-up 5 — The Slow-Subscriber Problem

    If one subscriber's onTrade callback takes 200ms (a rogue GC pause, a database write, whatever), every other subscriber for that trade waits behind it. On a hot symbol, one slow subscriber can back-pressure the entire distribution loop.…

  • Follow-up 6 — What About Reentrant unsubscribe in the Async World?

    In V4 (sync), a callback calling unsubscribe reentered the same thread and worked. In V5 (async), the callback runs on a pool thread. It can call unsubscribe normally — the mutation goes through the concurrent-collections path, no reentr…

  • Where to Stop

    For a 45-minute interview, V4 with ConcurrentHashMap + ConcurrentSkipListMap + CopyOnWriteArrayList is a strong stopping point. Bring up the executor and slow-subscriber problem as follow-ups if there's time. Don't try to draw V5 unless…

  • Summary of the Progression

    | Version | State | onTrade cost | Concurrency | What it fixes | |---|---|---|---|---| | V1 | ArrayList | O(total) | None — broken | Baseline | | V2 | ArrayList + lock + AtomicInteger | O(total) | Single global lock | CME, duplicate id,…

← back to Low Level Design

Trade Subscription Manager (LLD) — Low Level Design | GoCrack | GoCrack