Real-Time Communication Patterns
How does a chat app instantly show new messages? How do stock tickers update in real-time? Standard HTTP is request-response: the server can't "push" data to clients. This article explores four techniques for achieving real-time updates.
The Problem: Server Can't Initiate
Traditional HTTP is client-initiated:
Client: "GET /messages" → Server: "Here are 10 messages" Client: "GET /messages" → Server: "Here are 10 messages (same ones)"
Problem: Server has new data but can't notify client!
Pattern 1: Short Polling
Client repeatedly asks server for updates at regular intervals.
sequenceDiagram
participant Client
participant Server
Client->>Server: GET /messages (request #1)
Server-->>Client: [] (no new messages)
Note over Client: Wait 5 seconds
Client->>Server: GET /messages (request #2)
Server-->>Client: [] (no new messages)
Note over Client: Wait 5 seconds
Client->>Server: GET /messages (request #3)
Server-->>Client: ["New message!"]
Implementation
// Client-side (JavaScript)
function shortPolling() {
setInterval(async () => {
const response = await fetch('/api/messages');
const messages = await response.json();
if (messages.length > 0) {
updateUI(messages);
}
}, 5000); // Poll every 5 seconds
}
shortPolling();
# Server-side (Flask)
@app.route('/api/messages')
def get_messages():
messages = db.get_new_messages(user_id)
return jsonify(messages)
Pros & Cons
✅ Pros:
- Extremely simple to implement
- Works with any HTTP server
- No special protocols needed
❌ Cons:
- Wasteful: 99% of requests return empty if updates are rare
- High latency: Up to polling interval (5s delay)
- Server load: N clients × polling frequency
- Bandwidth waste: HTTP headers on every request
Best For
- Very simple apps with few users
- Updates can tolerate 5-10 second delays
Pattern 2: Long Polling (Comet)
Client sends request, server holds it open until new data arrives.
sequenceDiagram
participant Client
participant Server
Client->>Server: GET /messages (request held open)
Note over Server: Wait... wait... new message arrives!
Server-->>Client: ["New message!"] (30s later)
Client->>Server: GET /messages (immediately reconnect)
Note over Server: Wait... wait...
Implementation
// Client-side
async function longPolling() {
while (true) {
try {
const response = await fetch('/api/messages/poll', {
// Long timeout
signal: AbortSignal.timeout(60000)
});
const messages = await response.json();
updateUI(messages);
// Immediately reconnect
} catch (error) {
// Timeout or error, reconnect after brief delay
await sleep(1000);
}
}
}
longPolling();
# Server-side (with timeout)
@app.route('/api/messages/poll')
def poll_messages():
timeout = 30 # Hold connection for max 30s
start_time = time.time()
while time.time() - start_time < timeout:
messages = db.get_new_messages(user_id)
if messages:
return jsonify(messages)
time.sleep(0.5) # Check every 500ms
return jsonify([]) # Timeout, return empty
Pros & Cons
✅ Pros:
- Lower latency than short polling (near-instant)
- Fewer requests than short polling
- Works with standard HTTP
❌ Cons:
- Still creates new HTTP connection for each response
- HTTP header overhead on every reconnect
- Connection timeout management complex
- Scales poorly (holds server resources per client)
Best For
- Applications needing lower latency than short polling
- When WebSocket infrastructure not available
Pattern 3: Server-Sent Events (SSE)
One-way channel from server to client over HTTP.
sequenceDiagram
participant Client
participant Server
Client->>Server: GET /events (opens stream)
Server-->>Client: data: message 1
Server-->>Client: data: message 2
Server-->>Client: data: message 3
Note over Client,Server: Connection stays open
Implementation
// Client-side
const evtSource = new EventSource('/api/events');
evtSource.onmessage = (event) => {
const data = JSON.parse(event.data);
updateUI(data);
};
evtSource.onerror = () => {
console.log('Connection lost, reconnecting...');
};
# Server-side (Flask)
@app.route('/api/events')
def stream():
def generate():
while True:
# Check for new data
messages = db.get_new_messages(user_id)
if messages:
for msg in messages:
yield f"data: {json.dumps(msg)}\n\n"
time.sleep(1)
return Response(generate(), mimetype='text/event-stream')
# Server-side (FastAPI)
from fastapi.responses import StreamingResponse
@app.get("/events")
async def events():
async def event_generator():
while True:
messages = await get_new_messages(user_id)
if messages:
for msg in messages:
yield f"data: {json.dumps(msg)}\n\n"
await asyncio.sleep(1)
return StreamingResponse(event_generator(), media_type="text/event-stream")
Pros & Cons
✅ Pros:
- Built into browsers (EventSource API)
- Automatic reconnection
- Efficient (single long-lived connection)
- Works over HTTP/1.1 and HTTP/2
❌ Cons:
- One-way only (server → client)
- Limited browser support for custom headers
- Connection limit per domain (6 in HTTP/1.1)
Best For
- Stock tickers, live scores, dashboards
- Notifications, progress updates
- Any server-to-client streaming
Pattern 4: WebSockets
Bidirectional, full-duplex communication over single TCP connection.
sequenceDiagram
participant Client
participant Server
Client->>Server: HTTP Upgrade: websocket
Server-->>Client: 101 Switching Protocols
Note over Client,Server: Connection upgraded to WebSocket
Client->>Server: message 1
Server->>Client: message 2
Client->>Server: message 3
Server->>Client: message 4
Note over Client,Server: Both can send anytime
Upgrade Handshake
Client → GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 Server → HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= # Connection now uses WebSocket protocol
Implementation
// Client-side
const ws = new WebSocket('ws://example.com/chat');
ws.onopen = () => {
console.log('Connected!');
ws.send(JSON.stringify({ type: 'join', room: 'general' }));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
updateUI(message);
};
ws.onclose = () => {
console.log('Disconnected, reconnecting...');
setTimeout(connectWebSocket, 1000);
};
// Send message
function sendMessage(text) {
ws.send(JSON.stringify({ type: 'message', text }));
}
# Server-side (Python with websockets library)
import asyncio
import websockets
import json
connected_clients = set()
async def chat_handler(websocket, path):
connected_clients.add(websocket)
try:
async for message in websocket:
data = json.loads(message)
# Broadcast to all connected clients
await asyncio.gather(
*[client.send(message) for client in connected_clients]
)
finally:
connected_clients.remove(websocket)
async def main():
async with websockets.serve(chat_handler, "localhost", 8765):
await asyncio.Future() # run forever
asyncio.run(main())
// Server-side (Node.js with ws library)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Client connected');
ws.on('message', (message) => {
const data = JSON.parse(message);
// Broadcast to all clients
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
Pros & Cons
✅ Pros:
- True bidirectional communication
- Low latency (no HTTP overhead after handshake)
- Efficient: Single connection, minimal overhead
- Full-duplex: Both sides send simultaneously
❌ Cons:
- More complex than HTTP
- Load balancer/proxy support needed
- Connection state management required
- Not RESTful (different mental model)
Best For
- Chat applications
- Multiplayer games
- Collaborative editing (Google Docs)
- Real-time dashboards
- Trading platforms
Comparison Table
| Feature | Short Polling | Long Polling | SSE | WebSockets |
|---|---|---|---|---|
| Latency | High (5-10s) | Low (~1s) | Very Low | Very Low |
| Server Load | Very High | High | Low | Very Low |
| Bandwidth | High | Medium | Low | Very Low |
| Bidirectional | ❌ No | ❌ No | ❌ No | ✅ Yes |
| Complexity | Very Simple | Simple | Simple | Moderate |
| Browser Support | ✅ All | ✅ All | ✅ Modern | ✅ Modern |
| Firewall/Proxy | ✅ Works | ✅ Works | ✅ Works | ⚠️ May need config |
| Best For | Simple apps | Medium apps | Notifications | Chat, games |
Real-World Examples
Facebook Messenger (WebSockets)
// Simplified Facebook-style chat
const socket = new WebSocket('wss://chat.facebook.com');
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'message') {
displayMessage(msg);
} else if (msg.type === 'typing') {
showTypingIndicator(msg.user);
} else if (msg.type === 'read') {
markAsRead(msg.messageId);
}
};
// Send typing indicator
inputField.addEventListener('input', () => {
socket.send(JSON.stringify({ type: 'typing', user: currentUser }));
});
Stock Market Dashboard (SSE)
# Server streams stock prices
@app.route('/stock-stream')
def stock_stream():
def generate():
while True:
prices = get_current_stock_prices(['AAPL', 'GOOGL', 'MSFT'])
yield f"data: {json.dumps(prices)}\n\n"
time.sleep(1) # Update every second
return Response(generate(), mimetype='text/event-stream')
// Client displays live prices
const eventSource = new EventSource('/stock-stream');
eventSource.onmessage = (event) => {
const prices = JSON.parse(event.data);
updateStockPrices(prices);
};
Slack (WebSockets with Fallback)
Slack uses WebSockets when available, falls back to long polling:
class SlackConnection {
connect() {
if ('WebSocket' in window) {
this.ws = new WebSocket('wss://slack.com/rtm');
this.ws.onmessage = this.handleMessage;
} else {
// Fallback to long polling
this.longPoll();
}
}
longPoll() {
fetch('/api/events/poll')
.then(res => res.json())
.then(data => {
this.handleMessage(data);
this.longPoll(); // Reconnect
});
}
}
Scaling Considerations
WebSocket Scaling Challenges
100,000 connected WebSocket clients = 100,000 open TCP connections = Significant server resources
Solutions:
- Load Balancing with Sticky Sessions
upstream websocket_backend {
ip_hash; # Same client → same server
server ws1.example.com;
server ws2.example.com;
}
server {
location /ws {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
- Message Broker for Cross-Server Communication
# Server 1 receives message, publishes to Redis
await redis.publish('chat:room:general', json.dumps(message))
# All servers subscribe to broadcast channel
async def broadcast_listener():
pubsub = redis.pubsub()
await pubsub.subscribe('chat:room:general')
async for message in pubsub.listen():
# Send to all local WebSocket connections
await broadcast_to_local_clients(message)
Interview Tips 💡
When discussing real-time communication in system design interviews:
- Start with requirements: "Do we need bidirectional communication or just server-to-client updates?"
- Compare options: "For notifications, SSE works well. For chat, we'd need WebSockets..."
- Discuss scaling: "With 1M concurrent connections, we'd need multiple WebSocket servers with Redis pub/sub..."
- Mention fallbacks: "Like Slack, we'd implement WebSocket with long polling fallback..."
- Consider alternatives: "For simple use cases, short polling might be good enough..."
- Real examples: "Facebook Messenger uses WebSockets for instant messaging..."
Related Concepts
- HTTP/2 and HTTP/3 — Improved protocols for web communication
- Load Balancing — Distributing WebSocket connections
- Message Queues — Pub/sub for cross-server WebSocket messaging
- CDN — Edge caching for WebSocket endpoints
- WebRTC — Peer-to-peer real-time communication
About ScaleWiki
ScaleWiki is an interactive educational platform dedicated to demystifying distributed systems, software architecture, and system design. Our mission is to provide high-quality, technically accurate resources for software engineers preparing for interviews or solving complex scaling challenges in production.
Read more about our Editorial Guidelines & Authorship.
Educational Disclaimer: The architectural patterns and system designs discussed in this article are based on common industry practices, technical whitepapers, and public engineering blogs. Actual implementations in enterprise environments may vary significantly based on specific product requirements, legacy constraints, and evolving technologies.
Related Articles
WebRTC (Peer-to-Peer Communication)
Web Real-Time Communication protocol enabling direct peer-to-peer audio, video, and data transfer between browsers without intermediary servers, powering low-latency applications like Zoom and Google Meet.
BitTorrent Protocol (P2P File Sharing)
Complete guide to peer-to-peer file sharing using BitTorrent protocol, covering torrent structure, piece exchange, tit-for-tat algorithm, DHT for decentralization, and real-world implementations powering massive file distribution networks.
DNS Architecture
The phonebook of the internet. How Domain Name System works, the hierarchy of Route 53, and recursive vs iterative resolution strategies.