Skip to content
LowLevelDesign Mastery

Real-time Communication

Keeping clients in sync with the server

Traditional HTTP: Client requests, server responds. Client must poll for updates.

HTTP polling where the client repeatedly asks the server for updates and usually gets no updates, wasting resources

Problems:

  • Client must keep asking - Client repeatedly polls server, even when no updates are available
  • Wastes bandwidth (empty responses) - Most polling requests return no data, wasting network resources
  • High latency (only checks periodically) - Updates are delayed until next poll, not real-time
  • Server overload (many requests) - Constant polling creates unnecessary load on the server

Solution: Real-time communication - Server pushes updates to client!


Bidirectional, persistent connection.

WebSocket upgrade from HTTP to a persistent bidirectional connection where client and server can send messages at any time

Characteristics:

  • Full-duplex (both can send) - Client and server can send messages independently at any time
  • Persistent connection - Connection stays open, avoiding connection overhead
  • Low latency - Messages are sent immediately without HTTP request/response overhead
  • Efficient (no HTTP overhead) - No HTTP headers for each message, just raw data
  • More complex - Requires connection management, reconnection logic, and state handling
  • Stateful (harder to scale) - Each connection maintains state, making horizontal scaling more complex

Server-to-client streaming over HTTP.

Server-sent events where the client opens GET /events and the server pushes a one-way stream of events over HTTP

Characteristics:

  • Simple (HTTP-based) - Uses standard HTTP, easy to implement and debug
  • Automatic reconnection - Browser automatically reconnects if connection drops
  • Works through proxies/firewalls - Uses standard HTTP, works in restricted network environments
  • One-way (server to client) - Server pushes data to client, but client can’t send data back through SSE
  • Not bidirectional - Client must use separate HTTP requests to send data to server
  • Limited browser support (better now) - Modern browsers support SSE well, but older browsers may not

Hold request open until data available.

Long polling where the server holds each client request open until data is available, then the client immediately sends another

Characteristics:

  • Works everywhere (HTTP) - Uses standard HTTP, works in any environment
  • Simple to implement - Easy to add to existing HTTP-based systems
  • Less efficient (many requests) - Requires many HTTP requests, each with overhead
  • Higher latency - Latency depends on polling interval, not truly real-time
  • Server holds connections - Server must maintain open connections, consuming resources

1. Handshake (HTTP Upgrade):

GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

Server responds:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

2. Connection Established - Now bidirectional!

💡 Tip: Click dropdown to switch between languages
"websocket_server.py
import asyncio
import websockets
import json
class WebSocketServer:
def __init__(self):
self.clients = set()
async def register_client(self, websocket):
"""Register new client"""
self.clients.add(websocket)
print(f"Client connected. Total: {len(self.clients)}")
async def unregister_client(self, websocket):
"""Unregister client"""
self.clients.discard(websocket)
print(f"Client disconnected. Total: {len(self.clients)}")
async def handle_client(self, websocket, path):
"""Handle client connection"""
await self.register_client(websocket)
try:
async for message in websocket:
# Handle incoming message
data = json.loads(message)
await self.process_message(websocket, data)
except websockets.exceptions.ConnectionClosed:
pass
finally:
await self.unregister_client(websocket)
async def process_message(self, websocket, data):
"""Process client message"""
message_type = data.get('type')
if message_type == 'chat':
# Broadcast to all clients
await self.broadcast({
'type': 'chat',
'user': data.get('user'),
'message': data.get('message'),
'timestamp': data.get('timestamp')
})
elif message_type == 'ping':
# Respond to ping
await websocket.send(json.dumps({'type': 'pong'}))
async def broadcast(self, message):
"""Broadcast message to all clients"""
if self.clients:
message_json = json.dumps(message)
await asyncio.gather(
*[client.send(message_json) for client in self.clients],
return_exceptions=True
)
async def send_to_client(self, websocket, message):
"""Send message to specific client"""
try:
await websocket.send(json.dumps(message))
except websockets.exceptions.ConnectionClosed:
await self.unregister_client(websocket)
# Start server
server = WebSocketServer()
start_server = websockets.serve(
server.handle_client,
"localhost",
8765
)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
💡 Tip: Click dropdown to switch between languages
"websocket_client.js
class WebSocketClient {
constructor(url) {
this.url = url;
this.ws = null;
this.reconnectInterval = 1000;
this.maxReconnectAttempts = 5;
this.reconnectAttempts = 0;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.onOpen();
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.onMessage(data);
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
this.onError(error);
};
this.ws.onclose = () => {
console.log('WebSocket closed');
this.onClose();
this.reconnect();
};
}
send(message) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message));
} else {
console.error('WebSocket not connected');
}
}
reconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
setTimeout(() => {
console.log(`Reconnecting... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
this.connect();
}, this.reconnectInterval * this.reconnectAttempts);
}
}
onOpen() {
// Override in subclass
}
onMessage(data) {
// Override in subclass
}
onError(error) {
// Override in subclass
}
onClose() {
// Override in subclass
}
close() {
if (this.ws) {
this.ws.close();
}
}
}
// Usage
const client = new WebSocketClient('ws://localhost:8765');
client.onMessage = (data) => {
if (data.type === 'chat') {
console.log(`${data.user}: ${data.message}`);
}
};
client.connect();
// Send message
client.send({
type: 'chat',
user: 'John',
message: 'Hello!',
timestamp: Date.now()
});

Client opens HTTP connection, server streams events:

GET /events HTTP/1.1
Host: example.com
Accept: text/event-stream
Cache-Control: no-cache

Server responds with stream:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
event: message
data: Hello World
event: update
data: {"user": "John", "status": "online"}
event: message
data: Goodbye
💡 Tip: Click dropdown to switch between languages
"sse_server.py
from flask import Flask, Response, jsonify
import json
import time
import threading
app = Flask(__name__)
@app.route('/events')
def stream_events():
"""SSE endpoint"""
def event_stream():
while True:
# Send event
data = {
'timestamp': time.time(),
'message': 'Server update'
}
yield f"event: update\ndata: {json.dumps(data)}\n\n"
time.sleep(1) # Send every second
return Response(
event_stream(),
mimetype='text/event-stream',
headers={
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no' # Disable buffering in nginx
}
)
@app.route('/notify')
def notify():
"""Trigger notification"""
# In real app, this would trigger event
return jsonify({'status': 'notification sent'})
if __name__ == '__main__':
app.run(threaded=True)
💡 Tip: Click dropdown to switch between languages
"sse_client.js
class SSEClient {
constructor(url) {
this.url = url;
this.eventSource = null;
}
connect() {
this.eventSource = new EventSource(this.url);
this.eventSource.onopen = () => {
console.log('SSE connected');
this.onOpen();
};
this.eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
this.onMessage(data);
};
// Listen for specific event types
this.eventSource.addEventListener('update', (event) => {
const data = JSON.parse(event.data);
this.onUpdate(data);
});
this.eventSource.onerror = (error) => {
console.error('SSE error:', error);
this.onError(error);
};
}
onOpen() {
// Override in subclass
}
onMessage(data) {
// Override in subclass
}
onUpdate(data) {
// Override in subclass
}
onError(error) {
// Override in subclass
}
close() {
if (this.eventSource) {
this.eventSource.close();
}
}
}
// Usage
const client = new SSEClient('/events');
client.onUpdate = (data) => {
console.log('Update:', data);
};
client.connect();

Client sends request, server holds it open:

💡 Tip: Click dropdown to switch between languages
"long_polling_server.py
from flask import Flask, jsonify, request
import time
import threading
app = Flask(__name__)
pending_requests = []
@app.route('/poll')
def poll():
"""Long polling endpoint"""
timeout = int(request.args.get('timeout', 30))
last_id = int(request.args.get('last_id', 0))
# Check for new data
new_data = get_data_since(last_id)
if new_data:
return jsonify(new_data)
# No data - hold request
event = threading.Event()
pending_requests.append({
'event': event,
'last_id': last_id,
'timeout': timeout
})
# Wait for data or timeout
if event.wait(timeout):
new_data = get_data_since(last_id)
return jsonify(new_data)
else:
return jsonify({'status': 'timeout'}), 200
@app.route('/notify')
def notify():
"""Trigger notification"""
# Wake up pending requests
for req in pending_requests:
req['event'].set()
pending_requests.clear()
return jsonify({'status': 'notified'})

PatternUse WhenDon’t Use When
WebSocketBidirectional needed, low latency, high frequencySimple one-way updates, HTTP-only environments
SSEServer-to-client only, simple implementation, HTTP-basedBidirectional needed, client-to-server messages
Long PollingWebSocket/SSE not available, simple use caseHigh frequency, low latency needed
Need bidirectional?
Yes → WebSocket
No → Need low latency?
Yes → SSE
No → Long Polling (fallback)

Managing connections is critical:

💡 Tip: Click dropdown to switch between languages
"connection_manager.py
from typing import Dict, Set
import asyncio
from datetime import datetime, timedelta
class ConnectionManager:
"""Manages WebSocket connections"""
def __init__(self):
self.connections: Dict[str, Set] = {}
self.heartbeat_interval = 30 # seconds
self.connection_timeout = 60 # seconds
async def add_connection(self, connection_id: str, websocket):
"""Add new connection"""
if connection_id not in self.connections:
self.connections[connection_id] = set()
self.connections[connection_id].add(websocket)
# Start heartbeat
asyncio.create_task(self.heartbeat(websocket))
async def remove_connection(self, connection_id: str, websocket):
"""Remove connection"""
if connection_id in self.connections:
self.connections[connection_id].discard(websocket)
if not self.connections[connection_id]:
del self.connections[connection_id]
async def heartbeat(self, websocket):
"""Send heartbeat to keep connection alive"""
try:
while True:
await asyncio.sleep(self.heartbeat_interval)
await websocket.send(json.dumps({'type': 'ping'}))
except:
await self.remove_connection('unknown', websocket)
async def broadcast(self, message, connection_id: str = None):
"""Broadcast message"""
targets = self.connections.get(connection_id, set()) if connection_id else set().union(*self.connections.values())
disconnected = set()
for ws in targets:
try:
await ws.send(json.dumps(message))
except:
disconnected.add(ws)
# Clean up disconnected
for ws in disconnected:
await self.remove_connection('unknown', ws)

🔄 WebSocket: Full-Duplex

WebSocket provides bidirectional, persistent connection. Best for chat, gaming, real-time collaboration.

📡 SSE: Simple Streaming

SSE is HTTP-based, server-to-client streaming. Simpler than WebSocket, automatic reconnection.

⏳ Long Polling: Fallback

Long polling holds requests open. Use when WebSocket/SSE not available. Less efficient but works everywhere.

🔌 Connection Management

Manage connections carefully: heartbeat, cleanup, reconnection. Critical for production systems.


  • Master Rate Limiting - controlling API usage
  • Review API Gateway - how real-time fits with gateways
  • Learn gRPC - gRPC also supports streaming