What the interviewer just asked
You paused after the prompt. Now gather requirements — write them on the board so the interviewer can course-correct early.
Functional questions to ask
- How does the client receive prices? Server-Sent Events (SSE) — one persistent HTTP connection, server pushes events. Spring’s
SseEmitteris the natural Java handle. - How many connections per user? One. All watched symbols share a single SSE stream.
- How does the client register symbols? REST calls after connect: add one symbol, remove one symbol, or replace the entire list (page switch).
- What does a price event look like? JSON payload with symbol and price; the client routes by symbol field.
- What happens on page switch? Old watchlist is discarded; new list replaces it in one call — not ten sequential deletes and adds.
- Does the server generate prices or receive them? Assume an upstream feed calls
publish(symbol, price)— your job is fan-out to subscribed users, not market data ingestion. - What if the user connects but watches nothing? Connection stays open; no events until they call
POST /watch.
Non-functional questions
- Single JVM or distributed? Start in-memory, single process unless they insist on multi-node.
- Thread-safe? Yes — connect, watch, disconnect, and publish can run on different threads concurrently.
- Latency expectations? Sub-second fan-out from tick arrival to SSE push is fine for a watchlist UI.
- Delivery guarantee for a single tick? At-most-once is acceptable — if a user misses one price during a page switch, the next tick arrives. Confirm this explicitly; it affects whether you need a per-user lock on replace.
- How many users / symbols? Order of thousands of concurrent connections, tens of symbols per user — in-memory maps are sufficient for v1.
- What happens on reconnect? Page refresh opens a new SSE connection; old emitter is completed and state is cleaned up on disconnect callback.
What you produce on the board
Functional
GET /connect?userId=U1— open SSE connection; returnSseEmitterPOST /watch?userId=U1&symbol=AAPL— add symbol to user’s watchlistDELETE /watch?userId=U1&symbol=AAPL— remove symbol from watchlistPUT /watch?userId=U1body["MSFT","GOOGL","AMZN"]— replace entire watchlist- Internal
publish(symbol, price)— fan out to all users watching that symbol - On disconnect (tab close, timeout, error) — remove user from all maps
Non-functional
- Thread-safe registry for concurrent connect/watch/disconnect/publish
- In-memory, single JVM for v1
- One SSE connection per user
- O(1) fan-out lookup by symbol via
symbolUsersmap - At-most-once tick delivery acceptable during page switch (unless interviewer says otherwise)
Out of scope (say explicitly)
- Multi-region deployment
- Durable message replay or event sourcing
- Kafka / Pulsar / Redis Pub/Sub as the primary design (discuss in scale-out section)
- Authentication and authorization
- Rate limiting on watch API
- Historical price charts or persistence
Board checklist
Copy this structure onto the whiteboard and tick items as you build:
[ ] APIs: connect, add, remove, replace
[ ] Three maps: userEmitters, userSymbols, symbolUsers
[ ] connect() — register emitter, init symbol set, wire callbacks
[ ] disconnect() — remove emitter, unregister from all symbols
[ ] addSymbol() / removeSymbol() — update both maps
[ ] replaceSymbols() — tear down old, register new
[ ] publish() — symbolUsers → userEmitters → emitter.send()
[ ] Thread safety — ConcurrentHashMap + newKeySet()
[ ] Page-switch race — gap acceptable? per-user lock if not
Scope sentence you can say out loud
“I’ll build a single-JVM stock price fan-out service with one SSE connection per user. REST endpoints manage the watchlist; three concurrent maps form a bidirectional index between users and symbols. On disconnect, I clean up all registrations. On publish, I look up watchers by symbol and push to their emitters. I’ll call out the page-switch gap and when a per-user lock is needed, then mention Kafka or Redis only if we need to scale beyond one JVM.”