Case Study — Real-Time Pricing Engine

A full HLD walkthrough using StockStump: design a system where asset prices move with live events and thousands of users trade in-play — covering fan-out, the concurrency invariant, and latency-arbitrage fairness.

case-studyreal-timewebsocketconcurrency

Prompt

Design a system where virtual player prices update from a live sports feed and users buy/sell in real time. Thousands of concurrent users; prices must feel live; trades must be correct and fair.

1. Requirements

Functional: ingest live match events → update prices; stream prices to clients; execute buy/sell against a balance; portfolio + leaderboard. Non-functional: low-latency price push (sub-second feel), correctness (no double-spend), fairness (no front-running the feed), high read fan-out.

2. Estimation (shapes the design)

Say 50k concurrent users during a match, each watching ~10 players → a price tick must fan out to many sockets, but the source of truth updates only a few times/sec. Read:write on prices is enormous → compute once, broadcast many.

3. High-level design

  • Price engine applies performance jumps on events + small ticks between them, writing the current price to Redis once.
  • Fan-out: publish ticks on Redis pub/sub; stateless WebSocket gateways push to clients. N clients ≠ N feed calls, and gateways scale horizontally.
  • Trade service validates and settles against Postgres.

4. Deep dive — the two hard parts

Correctness (no double-spend). The balance check must be atomic, not a read-modify-write in app memory. One statement:

UPDATE users SET balance = balance - :cost
WHERE id = :id AND balance >= :cost;   -- 0 rows ⇒ insufficient funds, reject

Alternatively optimistic locking (@Version) with retry, or SELECT … FOR UPDATE under high contention.

Fairness (latency arbitrage). The feed lags real life 15–40s, so someone at the venue can front-run it. Mitigations, cheapest first:

  1. Trade lock: on any event, set player_trade_lock:{id} in Redis (60s TTL); the trade endpoint rejects locked players — closes the window with one O(1) check.
  2. Delayed execution queue: orders fill 30s later at the price then.
  3. Sealed batch settlement at a window's median price.
This is the moment to show judgment

Don't just list fixes — recommend one and justify it: "ship the trade lock first (2 hours, closes the exploit), add anomaly detection, and move to a delayed queue only if stakes rise." Sequencing by cost/impact is the signal.

5. Bottlenecks & scale

  • WS gateway memory/conns → shard by player or user; add gateways behind an LB.
  • Redis hot key for a star player → replicas / local read cache on gateways.
  • Settlement throughput → a queue + idempotent workers (idempotency key per order so retries don't double-execute).

Design drills

Real-time pricing is fan-out + atomicity + fairness. Drill all three.

Design drills: Real-time pricing0/4 done

Whiteboard each one out loud for 5–10 minutes before you reveal what a strong answer covers — the gap between your sketch and the checklist is your study list. Progress is saved on this device.

Warm-up

How does a price get from a match event to thousands of clients in real time?

Core

A player's price must update correctly under many concurrent trades. How do you avoid a lost update?

Core

Some users see the match event before your feed does — latency arbitrage. Defend against it.

Stretch

Scale the WebSocket gateway to millions of concurrent connections.