[!NOTE] HTTP is a request-response protocol — the client asks, the server answers. But many features need the server to push data to the client without being asked: chat messages, live scores, stock prices, notifications. This chapter covers four approaches to real-time communication, from simple to sophisticated.
The Four Approaches
| Method | How It Works | Latency | Server Load | Bidirectional? |
|---|---|---|---|---|
| Short Polling | Client asks "Any updates?" every N seconds | Up to N seconds | High (constant requests) | No |
| Long Polling | Client asks, server holds connection until there is data | Near-instant | Medium (held connections) | No |
| Server-Sent Events (SSE) | Server pushes events over a persistent HTTP connection | Near-instant | Low | No (server → client only) |
| WebSocket | Full-duplex connection after HTTP upgrade handshake | Instant | Low per message, but connection management overhead | Yes |
Short Polling
Client: GET /api/messages?since=12345 → Server: [] (empty)
... 5 seconds later ...
Client: GET /api/messages?since=12345 → Server: [] (empty)
... 5 seconds later ...
Client: GET /api/messages?since=12345 → Server: [{id: 12346, text: "Hi!"}]
Simple but wasteful. If you poll every 5 seconds with 1 million connected users, that is 200,000 requests/second — most returning empty responses.
Long Polling
Client: GET /api/messages?since=12345
... Server holds the connection open ...
... 30 seconds of waiting ...
... A new message arrives! ...
Server: [{id: 12346, text: "Hi!"}]
Client: GET /api/messages?since=12346 → (immediately reconnects)
Much more efficient than short polling. The server only responds when there is actual data. Used by early Facebook chat and many notification systems.
Server-Sent Events (SSE)
A persistent, one-directional HTTP connection where the server pushes events to the client. The client opens a connection, and the server sends events as they occur:
Client: GET /api/events (Accept: text/event-stream)
Server: data: {"type": "message", "text": "Hello"}\n\n
...
Server: data: {"type": "message", "text": "World"}\n\n
...
(Connection stays open)
Pros: Built into every browser (EventSource API), automatic reconnection, works through standard HTTP infrastructure (proxies, CDNs). Con: Server-to-client only — client cannot send data back over the same connection.
Use case: Live dashboards, stock tickers, GitHub Actions build logs, notification feeds.
WebSocket
A true full-duplex, bidirectional connection. The client initiates an HTTP request with an Upgrade: websocket header. If the server agrees, the connection is "upgraded" to WebSocket protocol (ws:// or wss://).
Client → Server: GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Server → Client: HTTP/1.1 101 Switching Protocols
Upgrade: websocket
... now both sides can send messages at any time ...
Client: {"type": "message", "text": "Hello"}
Server: {"type": "message", "text": "Hi back!"}
Server: {"type": "typing", "user": "Alice"}
Client: {"type": "message", "text": "What''s up?"}
Scaling WebSockets with Redis Pub/Sub
With 1 million concurrent connections across 20 WebSocket servers, how does a message from User A (on Server 3) reach User B (on Server 7)?
User A → [WS Server 3] → PUBLISH to Redis → All WS servers subscribe
→ [WS Server 7] → User B
Redis pub/sub acts as a broadcast bus. When Server 3 receives a message, it publishes to a Redis channel. All 20 servers are subscribed and forward the message to the correct local connections.
Real-World Usage
- Slack: WebSockets for real-time messaging. Falls back to long polling for restrictive corporate networks.
- Uber: WebSockets for live driver location streaming to the rider''s phone. The driver sends GPS coordinates over WebSocket; the server fans them out.
- GitHub: SSE for live build logs and notification updates. WebSocket for Codespaces terminal sessions.
Common Mistakes
- ❌ Using WebSocket when SSE is sufficient — if you only need server-to-client updates (dashboard, notifications), SSE is simpler and works better with HTTP/2.
- ❌ Forgetting reconnection logic — WebSocket connections drop frequently (mobile networks, proxy timeouts). Always implement auto-reconnect with backoff.
- ❌ Putting all connections on one server — use Redis pub/sub or a message broker to fan out events across multiple WebSocket servers.
- ❌ Not implementing heartbeats — without periodic ping/pong frames, dead connections hang forever. Use 30-second heartbeat intervals.
Scaling WebSocket: Multi-Server Architecture
[Load Balancer]
(sticky sessions by user_id)
/ | \
[WS Server 1] [WS Server 2] [WS Server 3]
(users A,B) (users C,D) (users E,F)
\ | /
[Redis Pub/Sub]
(broadcast channel)
When user A sends message to user D:
WS Server 1 receives message from user A
WS Server 1 publishes to Redis channel "chat:room:123"
WS Server 2 (subscribed) receives via Redis
WS Server 2 pushes to user D''s WebSocket connection
Connection Capacity Planning
| Server Config | Max Connections | Memory per Connection |
|---|---|---|
| Node.js (1 core, 1GB RAM) | ~10,000-50,000 | ~20 KB |
| Go (1 core, 1GB RAM) | ~100,000-500,000 | ~4 KB |
| Erlang/Elixir (Phoenix) | ~1,000,000-2,000,000 | ~2 KB |
Discord''s approach: Discord handles ~5 million concurrent WebSocket connections using Elixir/Erlang (BEAM VM), which excels at massive concurrent connections due to its lightweight process model.
[!TIP] Key Takeaways:
• Short polling: simple but wasteful. Only for low-frequency, non-critical updates.
• Long polling: good middle ground, used by early chat systems.
• SSE: best for server-to-client streams (dashboards, logs, notifications). Built-in browser support.
• WebSocket: the only option for bidirectional real-time communication (chat, gaming, collaboration).
• Scale WebSockets using Redis pub/sub + sticky sessions. Consider Go or Elixir for high connection counts.