Building a Responsive Real-Time Dashboard with WebSockets
Techniques for handling high-frequency tick streams without browser UI freezing, memory leaks, or stale connections.
The UI Thread Choke Point in High-Frequency Streams#
When building real-time dashboards that stream live financial data—such as foreign exchange pairs updating multiple times per millisecond—naive front-end architectures collapse under event volume. Dispatching a React state update for every incoming WebSocket packet overwhelms the virtual DOM reconciliation engine, drops the browser framerate below 15 FPS, and completely freezes user interactions like scrolling and chart zooming.
Decoupling Packet Ingestion from Rendering Cycles#
The core architectural principle is decoupling network arrival from UI rendering. The human eye cannot perceive updates occurring faster than 60 frames per second (16.6ms intervals), yet financial WebSocket feeds often deliver 300 to 500 messages per second during volatile trading sessions.
In my forex dashboard work, incoming socket ticks were written directly to an in-memory mutable ring buffer. An independent rendering loop synchronized to requestAnimationFrame samples the buffer at the screen's native refresh rate:
class TickBuffer {
private buffer: Map<string, PriceTick> = new Map();
private dirty = false;
public ingest(tick: PriceTick): void {
this.buffer.set(tick.symbol, tick);
this.dirty = true;
}
public flush(): Map<string, PriceTick> | null {
if (!this.dirty) return null;
this.dirty = false;
return new Map(this.buffer);
}
}Offloading Payload Parsing to Web Workers#
Parsing large JSON payloads and computing moving averages on the main JavaScript thread causes perceptible micro-stutters. By moving WebSocket connections into a dedicated Web Worker, JSON parsing and binary decompression execute entirely off the UI thread. The worker aggregates price ticks over an 8ms window and posts batched typed arrays to the UI thread via postMessage using zero-copy ArrayBuffer transferables.
Resilient Reconnection: Avoiding the Thundering Herd#
Network disconnections are inevitable in long-lived client sessions. Naive reconnect algorithms with fixed retry intervals create a thundering herd problem that overwhelms recovering edge servers. A robust client implements exponential backoff with full jitter:
function calculateBackoff(attempt: number, baseMs = 500, maxMs = 30000): number {
const exponential = Math.min(maxMs, baseMs * Math.pow(2, attempt));
// Full jitter: uniformly distributed random delay between 0 and exponential limit
return Math.floor(Math.random() * exponential);
}State Resynchronization via Sequence Watermarks#
Simply reopening a socket connection leaves the client with stale or missing data during the downtime gap. Every server event carries a monotonically increasing sequence watermark. Upon successful reconnect, the client transmits its last observed sequence ID; the server replays missed events from a Kafka buffer or sends a fresh snapshot if the gap exceeds retention windows.

Nishant Dev
Full-Stack & Backend Systems Engineer · Chennai, India
Building high-throughput distributed systems, Kafka streaming backends, and modern responsive interfaces from Chennai, India.