I built . Instead of mounting a DOM node per message, it only renders the rows that are actually inside the visible scroll viewport — roughly 12–15 at a time — and recycles them as the user scrolls. The feed can hold 50 messages or 50,000; the DOM footprint stays constant either way.
After switching over, the same spike test — thousands of messages, fired as fast as the mock stream could generate them — held a steady 60fps, and the DOM node count settled at 14, regardless of how high the message count climbed.
The FPS number is the visible symptom. The DOM node count is the actual mechanism, and it's the more interesting one, because it holds true even before performance visibly degrades — it's not something you'd necessarily notice by eye until the spike hits, but it's there from message one.
A few other load-bearing decisions
Virtualization solved the big one, but a few smaller choices mattered too:
Reaction animations are capped at 8 concurrent bubbles. Reactions can arrive multiple times a second in a busy stream, and animating every single one would spawn DOM nodes faster than the browser could clean them up. Capping the count doesn't lose meaningful reactions — they arrive faster than a human can register each one individually anyway.
Chat history is capped at 5,000 messages client-side. A long-running stream shouldn't grow memory without bound, so old messages page out once the cap is hit — the same pattern a production client would need.
The mock event stream sits behind a single callback interface.startLiveEventStream(onEvent)is the entire contract. Every component downstream — chat, reactions, viewer count — only depends on that event shape, not on how the events arrive. Swapping thesetIntervalmock for a real WebSocket connection later would mean changing exactly one file.
What I'd do differently with more time
The FPS readout is honest but manual — I watched the number and wrote down what I saw. A more rigorous version would script the spike test and assert against a performance trace automatically, the way you'd want a real regression test to work rather than something a developer eyeballs once and trusts forever. That's the next thing I'd build if I kept going.
LiveShop is open source on GitHub — the README has the full before/after numbers and architecture notes if you want to dig deeper.
SOCIAL SHARE CARD GENERATOR