What you should do next
With requirements agreed, publish a timeboxed plan before drawing classes. Interviewers score the plan as much as the code.
45-minute timeline
| Phase | Clock | Output |
|---|---|---|
| Requirements | 0:00 – 5:00 | Functional + NFR + out-of-scope |
| Three maps | 5:00 – 12:00 | Bidirectional index diagram + three traversal paths |
| Connect / disconnect | 12:00 – 20:00 | SseEmitter lifecycle, callbacks, full cleanup |
| Watch API | 20:00 – 28:00 | add, remove, replace with state-after examples |
| Publish / fan-out | 28:00 – 35:00 | symbolUsers → userEmitters → send |
| Concurrency / races | 35:00 – 42:00 | ConcurrentHashMap, page-switch gap, per-user lock |
| Scale-out wrap-up | 42:00 – 45:00 | Why in-memory fails across pods; Kafka mention |
If you run short on time, protect the three maps and connect/disconnect. A working registry with fan-out beats a half-drawn Kafka diagram every time.
Delivery order (matches this course)
- APIs on the board — four REST endpoints plus internal
publish - Three maps before any code — draw the bidirectional index and name all three traversal paths
- Connect and disconnect — emitter registration and full cleanup on close
- Symbol management — add, remove, replace; show state after each operation
- Fan-out — publish walks
symbolUsers, sends to each emitter - Thread safety — concurrent maps, thread-safe sets, optional per-user lock
- Scale-out talk — sticky sessions or external pub-sub; only after single-JVM design is solid
Lead with the bidirectional index, not Redis
The instinct under time pressure is to say “I’ll use Redis Pub/Sub.” That skips the part the interviewer is testing: how do you maintain a registry that supports fan-out, per-user cleanup, and symbol add/remove in O(1)?
Say this instead:
“Inside one JVM, I need two directions of lookup: given a user, what symbols do they watch? Given a symbol, who is watching it? That is a bidirectional index — two maps kept in sync. The third map holds the live SSE connection handle per user.”
Redis solves cross-process fan-out. The three-map design solves in-process registry correctness. You need the second before the first makes sense.
WRONG order: Kafka → consumer groups → somehow SSE
RIGHT order: three maps → connect/disconnect → fan-out → then Kafka if multi-JVM
Patterns you will use (name them)
- Bidirectional index —
userSymbolsandsymbolUserskept consistent on every mutation - Registry with lifecycle callbacks —
SseEmitter.onCompletion/onTimeout/onError → disconnect - Fan-out via inverted index —
symbolUsers[symbol]gives all subscribers without scanning every user - Per-user lock — serialize replace for one user without blocking others
Where concurrency fits
Do not lead with distributed locks. Lead with:
- All three maps are
ConcurrentHashMap - Sets inside them are
ConcurrentHashMap.newKeySet()— thread-safe for concurrent add/remove during fan-out replaceSymbolshas a brief window where the user is registered under neither old nor new symbols — acceptable for a price feed unless the interviewer requires atomic swap- Per-user lock makes replace atomic without a global bottleneck
What to say at each phase transition
After requirements:
“Single JVM, thread-safe, one SSE per user. I’ll draw the three maps next.”
After three maps:
“Fan-out goes symbol → users → emitters. Disconnect walks user → symbols and unregisters. Let me implement connect first.”
After connect/disconnect:
“Connection is open but idle until watch calls. Add and remove update both maps. Replace is a page switch — I’ll show the gap.”
After fan-out:
“This works on one JVM. With multiple pods, each pod only knows its own connections — I’d need sticky sessions or an external pub-sub layer. Happy to sketch that if we have time.”
That pacing keeps you inside 45 minutes and signals senior judgment about when infrastructure belongs in the conversation.