WebSocket & Real-Time Architecture: Patterns for Chat, Gaming, and Live Data
When your app needs data the instant it changes — chat messages, stock prices, live scores, multiplayer game state — you need real-time architecture.
HTTP request-response doesn't cut it. This guide covers the protocols, patterns, and scaling strategies for building real-time systems.
Real-Time Protocols#
WebSocket#
Full-duplex, persistent connection. Both client and server can send messages anytime.
Client ← → Server (persistent TCP connection)
Best for: Chat, gaming, collaborative editing, live dashboards Latency: < 50ms Scaling: Requires sticky sessions or pub/sub fan-out
Server-Sent Events (SSE)#
Server pushes events to client. One-directional (server → client only).
Client ← Server (HTTP stream, auto-reconnect)
Best for: Notifications, live feeds, stock tickers Latency: < 100ms Scaling: Easier than WebSocket (HTTP-compatible, works with CDNs)
Long Polling#
Client makes HTTP request, server holds it until data is available.
Client → Server (request) ... waits 30s ... ← response
Client → Server (immediately re-request) ... waits ...
Best for: Fallback when WebSocket isn't available Latency: 0-30s (variable) Scaling: Simplest, but highest server resource usage
Comparison#
| Feature | WebSocket | SSE | Long Polling |
|---|---|---|---|
| Direction | Bidirectional | Server → Client | Server → Client |
| Protocol | WS/WSS | HTTP | HTTP |
| Reconnect | Manual | Automatic | Manual |
| Binary data | Yes | No (text only) | Yes |
| CDN compatible | No | Yes | Yes |
| Complexity | Medium | Low | Low |
| Best for | Chat, gaming | Notifications, feeds | Fallback |
Architecture Patterns#
1. Chat Application#
Client A → WebSocket Gateway → Message Router
Client B → WebSocket Gateway → Message Router
↓
Redis Pub/Sub
↓ ↓
WS Gateway 1 WS Gateway 2
(Client A) (Client B)
↓
Message Store (PostgreSQL)
Presence Service (Redis)
Key decisions:
- Message ordering: Per-channel sequence numbers
- Delivery guarantee: At-least-once (client dedup by message ID)
- Presence: Track online users in Redis with TTL heartbeats
2. Live Dashboard#
Data Source → Kafka → Stream Processor → Redis (latest values)
↓
SSE/WebSocket Gateway
↓ ↓
Dashboard Dashboard
(User 1) (User 2)
Key decisions:
- SSE is simpler and sufficient (server → client only)
- Throttle updates to 1/second for UI (even if data changes faster)
- Cache latest state in Redis for new connections
3. Multiplayer Game#
Player A → WebSocket → Game Server (authoritative state)
Player B → WebSocket → Game Server
Player C → WebSocket → Game Server
↓
State broadcast every 50ms (20 ticks/sec)
Input processing + physics
Anti-cheat validation
Key decisions:
- Server authoritative: Server owns game state, clients send inputs
- Client prediction: Client predicts locally, server corrects
- Tick rate: 20-60Hz depending on game type
- Interpolation: Smooth movement between server updates
4. Collaborative Editing (Google Docs)#
User A types → Local CRDT → Broadcast delta via WebSocket
User B types → Local CRDT → Broadcast delta via WebSocket
↓
Sync Server (merge CRDTs)
↓
Document Store
Key decisions:
- CRDT (Conflict-free Replicated Data Type) for automatic merge
- OT (Operational Transform) as alternative (Google Docs uses this)
- Cursor positions broadcast separately (high frequency, no persistence)
5. Notifications#
Event occurs → Notification Service → check preferences
→ Redis Pub/Sub
→ SSE to connected clients
→ Push notification (FCM/APNs) for offline
→ Email for important notifications
Key decisions:
- SSE for in-app (simpler than WebSocket for one-way)
- Fallback to push/email for offline users
- User preferences control which channels
Scaling WebSocket#
The hard part: WebSocket connections are stateful. A user connected to Server A can't receive messages routed to Server B.
Pattern 1: Pub/Sub Fan-Out (Redis)#
Server A (1000 connections) ←→ Redis Pub/Sub ←→ Server B (1000 connections)
←→ Server C (1000 connections)
Every message published to Redis. All servers subscribe and forward to their connected clients.
Pattern 2: Sticky Sessions#
Load balancer routes same user to same server:
User ABC → hash("ABC") → always Server A
User DEF → hash("DEF") → always Server B
Simpler but limits horizontal scaling. Server failure disconnects all its users.
Pattern 3: Dedicated Connection Servers#
Separate WebSocket servers from business logic:
Client → WS Connection Server (stateless routing)
→ publishes to Kafka/Redis
→ subscribes to user's channels
Business Logic Servers → publish events to Kafka
→ WS servers fan out to clients
Connection servers handle millions of idle connections. Business logic scales independently.
Connection Management#
Heartbeats#
Detect dead connections:
Server → ping every 30s → Client responds pong
No pong in 60s → close connection → client reconnects
Reconnection#
Clients must handle disconnections gracefully:
const connect = () => {
const ws = new WebSocket(url);
ws.onclose = () => {
// Exponential backoff: 1s, 2s, 4s, 8s, max 30s
setTimeout(connect, Math.min(30000, 1000 * 2 ** retries++));
};
ws.onopen = () => { retries = 0; };
};
Message Queuing#
Buffer messages during disconnection:
Client disconnects → server queues messages in Redis (TTL: 5min)
Client reconnects → server sends queued messages → resume real-time
Tools & Libraries#
| Tool | What | Best For |
|---|---|---|
| Socket.io | WebSocket + fallbacks | Node.js apps, broad browser support |
| ws | Raw WebSocket (Node.js) | Performance, minimal overhead |
| Ably | Managed real-time | Chat, notifications without infra |
| Pusher | Managed WebSocket | Simple pub/sub, presence |
| LiveKit | WebRTC SFU | Video/audio, screen sharing |
| Phoenix Channels | Elixir real-time | High-concurrency, fault-tolerant |
| Centrifugo | Real-time server | Language-agnostic, scalable |
When NOT to Use WebSocket#
| Scenario | Use Instead |
|---|---|
| One-way server updates | SSE (simpler, HTTP-compatible) |
| Infrequent updates (< 1/min) | Polling or SSE |
| Static content delivery | HTTP + CDN |
| Request-response APIs | REST or GraphQL |
| Serverless (Lambda) | SSE or polling (WS needs persistent connections) |
Summary#
- WebSocket for bidirectional, low-latency (chat, gaming)
- SSE for server-push only (notifications, feeds) — simpler to scale
- Scale with pub/sub — Redis or Kafka for fan-out across servers
- Always handle reconnection — exponential backoff + message queuing
- Separate connection from logic — WS servers scale differently than business logic
- Don't use WebSocket for everything — SSE and polling have their place
Design real-time architectures at codelit.io — generate interactive diagrams with WebSocket, Kafka, and Redis patterns.
Try these templates
Uber Real-Time Location System
Handles 5M+ GPS pings per second using H3 hexagonal geospatial indexing.
6 componentsReal-Time Collaborative Editor
Notion-like document editor with real-time collaboration, conflict resolution, and rich media.
9 componentsNetflix Video Streaming Architecture
Global video streaming platform with adaptive bitrate, CDN distribution, and recommendation engine.
10 componentsContinue learning
Go deeper on system design
As an Amazon Associate I earn from qualifying purchases. Codelit may receive a commission at no extra cost to you.
Building Microservices
Sam Newman · 2021
600 pages on splitting a monolith: service boundaries, data decomposition, migration order.
Database Internals
Alex Petrov · 2019
Explains B-trees, LSM trees and consensus protocols so you can predict how a datastore will behave.
4.6 (560)#2 in Management Information SystemsRelease It!
Michael T. Nygard · 2018
Post-mortems of real outages, then the stability patterns (circuit breaker, bulkhead) that stop them.
4.6 (501)#4 in Client-Server Networking SystemsGraphQL in Action
Samer Buna · 2021
Builds schema, resolvers and N+1 fixes end to end rather than stopping at a toy query.
4.0 (18)