WebSockets & Server-Sent Events
Real-time communication protocols: WebSocket full-duplex connections vs SSE one-way streams, connection management, and scaling challenges.
Why Real-Time?
HTTP's request-response model requires the client to initiate every interaction. For applications that need to push data to clients — chat messages, live scores, collaborative editing, stock tickers, order status updates — a mechanism that allows the server to send data without a prior client request is essential.
WebSockets
WebSocket is a protocol (RFC 6455) that upgrades an HTTP connection into a full-duplex, persistent, bidirectional channel. Either the client or the server can send messages at any time without the overhead of HTTP headers on each exchange.
// Client-side WebSocket
const ws = new WebSocket('wss://chat.example.com/room/42');
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'join', userId: '123' }));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
renderMessage(msg);
};
ws.onerror = (error) => console.error('WebSocket error:', error);
ws.onclose = (event) => {
console.log('Disconnected. Code:', event.code);
// Reconnect with exponential backoff
};- Full duplex: Both client and server can send messages independently, simultaneously.
- Low overhead: After the handshake, messages have only 2-14 bytes of framing overhead vs. ~800 bytes of HTTP headers.
- Persistent connection: No handshake cost per message (unlike HTTP/1.1 polling).
- Binary support: Can send binary frames (ArrayBuffer), not just text — useful for game state, images, or audio.
Server-Sent Events (SSE)
Server-Sent Events is a simpler protocol built on plain HTTP. The server opens a long-lived HTTP response and streams text events to the client. The client can only receive data (one-way), but it gets automatic reconnection and event ID tracking for free via the browser's `EventSource` API.
// Client: EventSource handles reconnection automatically
const evtSource = new EventSource('/api/stream/orders');
evtSource.addEventListener('order_update', (event) => {
const update = JSON.parse(event.data);
updateOrderStatus(update);
});
evtSource.onerror = () => {
// EventSource auto-reconnects after a delay
console.log('Connection lost, reconnecting...');
};# SSE response format (plain text)
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
id: 1
event: order_update
data: {"orderId": "ABC", "status": "shipped"}
id: 2
event: order_update
data: {"orderId": "XYZ", "status": "delivered"}
: heartbeat comment (keeps connection alive)WebSocket vs SSE vs Polling Comparison
| Aspect | WebSocket | SSE | Long Polling |
|---|---|---|---|
| Direction | Full duplex (bidirectional) | Server to client only | Server to client only |
| Protocol | ws:// / wss:// | Plain HTTP | Plain HTTP |
| Browser support | All modern browsers | All modern browsers (no IE) | Universal |
| Auto-reconnect | Must implement manually | Built into EventSource | Must implement manually |
| Proxy/firewall friendly | Sometimes blocked | Yes — plain HTTP | Yes — plain HTTP |
| Multiplexing over HTTP/2 | No (separate connection) | Yes | Yes |
| Use case | Chat, collaborative editing, gaming | Notifications, live feeds, dashboards | Fallback for environments blocking WS/SSE |
| Server load | High (persistent connections) | High (persistent connections) | Medium (connections cycle) |
Scaling WebSocket Connections
WebSockets are stateful — a connection is pinned to a specific server. This creates challenges when scaling horizontally:
- Sticky sessions required: The load balancer must route all requests from a given WebSocket client to the same server. If the server restarts, the connection is lost.
- Message fan-out: If a message should be delivered to multiple connected clients (e.g., a chat room with users on different servers), you need a pub/sub backbone like Redis Pub/Sub or Kafka to broadcast across server instances.
- Connection limits: A single server can realistically handle 10,000-100,000 concurrent WebSocket connections (depending on message frequency and memory). Design for horizontal scaling.
- Heartbeats and keepalives: Send periodic ping frames to detect dead connections and keep the TCP connection through NAT devices and firewalls that have connection timeouts.
Real-world: Slack's WebSocket architecture
Slack uses WebSockets for real-time message delivery. Each server handles thousands of persistent connections. Redis Pub/Sub fans messages out to all servers in a workspace's connection pool. When a server becomes overloaded or restarts, clients reconnect automatically with exponential backoff and receive missed messages by replaying from the point their connection dropped (tracked via message IDs).
Interview Tip
When asked to design a real-time system (chat, notifications, live collaboration), explicitly address: (1) connection management — WebSocket vs SSE based on bidirectionality needs, (2) message persistence — where do you store messages so reconnecting clients can replay missed events, (3) fan-out — how do you deliver to all connected clients of a user who is connected to multiple servers, and (4) horizontal scaling — Redis Pub/Sub or Kafka for cross-server message delivery. These four points show architectural maturity.
Practice this pattern
Design a real-time collaborative document editor