The Problem: HTTP Is a One-Way Street
In 2012, Slack was struggling. Their chat app needed messages to appear instantly, but HTTP only works like sending letters: the client asks a question, the server sends an answer, done. Their early prototype polled the server every 2 seconds - 30 requests per minute per user, most returning 'nothing new.' With 10,000 users online, that was 300,000 useless requests per minute. The servers groaned under the load and messages still felt laggy.
This is the fundamental problem with HTTP for real-time applications: the server has no way to push data the moment something happens. The client has to keep asking. WebSockets and SSE exist to solve this.
Three Approaches Compared
Polling: Client keeps asking
Client: Anything new? Server: Nope.
Client: Anything new? Server: Nope.
Client: Anything new? Server: Yes! Here's a message.
Client: Anything new? Server: Nope.
[30 requests/min, ~1s average latency]
SSE: Server pushes when ready (one-way)
Client: I'm listening. Server: ...waiting...
Server: Here's an event!
Server: Here's another!
Server: ...waiting...
[1 persistent connection, 0ms latency, server-to-client only]
WebSocket: Full two-way pipe
Client: Upgrade! Server: OK, upgraded!
Client: Chat message -> Server
Server -> New message -> Client
Client: Typing... -> Server
Server -> User online -> Client
[1 persistent connection, 0ms latency, both directions]
WebSockets: A Two-Way Pipe
WebSocket is a protocol that starts as HTTP, then upgrades into a persistent, full-duplex connection. Both sides can send messages at any time.
The upgrade handshake looks like this:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
--- Server Response ---
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
After this handshake, the TCP connection stays open and both sides send lightweight frames back and forth. No HTTP headers on every message - just raw data with a tiny 2-6 byte frame header. This is why WebSockets are so efficient for high-frequency messaging.
WebSockets are ideal for:
- Chat applications - messages flow both ways instantly (Slack, Discord, WhatsApp Web)
- Multiplayer games - player actions and game state need constant bidirectional exchange
- Collaborative editing - every keystroke needs to reach other editors immediately (Google Docs, Figma)
- Trading platforms - order submissions (client to server) and price feeds (server to client) both need to be real-time
Server-Sent Events (SSE): One-Way Streaming
Sometimes you only need the server to push data to the client. The client never needs to send anything back through this channel (it can use regular HTTP for that). SSE is perfect for this.
SSE uses a regular HTTP connection that the server keeps open, sending events as they happen. The format is dead simple - just text lines:
// Client-side JavaScript - that's it!
const source = new EventSource('/api/live-prices');
source.addEventListener('price-update', (e) => {
const data = JSON.parse(e.data);
updatePriceDisplay(data.symbol, data.price);
});
// Server sends plain text events:
event: price-update
data: {"symbol": "AAPL", "price": 178.50}
id: 1042
event: price-update
data: {"symbol": "GOOG", "price": 141.20}
id: 1043
SSE advantages over WebSockets:
- Works over standard HTTP - no special proxy configuration needed, passes through all firewalls and corporate proxies
- Automatic reconnection - browsers automatically reconnect if the connection drops, and send the Last-Event-ID header so the server can resume from where it left off
- Event IDs and types - built-in support for named events and sequence tracking
- Simpler to implement - just stream text over HTTP. No frame protocol, no special libraries needed. Any HTTP server can serve SSE.
When to Use Which
Use Case Best Choice Why
------------------------------------------------------------
Chat / messaging WebSocket Bidirectional + low latency
Live notifications SSE Server-push only, simpler
Multiplayer gaming WebSocket Low-latency bidirectional
Live dashboards SSE Server pushes metrics
Stock ticker SSE Price feed is one-way
Collaborative edit WebSocket Both users send changes
Progress updates SSE Server reports status
IoT device control WebSocket Send commands + receive data
Scale Challenges
Both WebSockets and SSE maintain persistent connections. A server with 50,000 concurrent WebSocket connections needs to manage all those open sockets. This introduces challenges that do not exist with stateless HTTP:
- Memory per connection - each connection uses ~20-50KB of memory. 100K connections = 2-5 GB just for connection state.
- Load balancer stickiness - you cannot freely route a reconnecting client to a different backend server because its subscription state lives on the original server.
- Horizontal scaling requires a pub/sub backbone - like Redis Pub/Sub or Kafka - so any server can push messages to any connected client regardless of which server holds the connection.
- Connection storms - if a server restarts, all 50K clients reconnect simultaneously. Add jittered reconnection delays to prevent thundering herd.
Scaling WebSockets: The Architecture
[Load Balancer]
/ | \
[WS-1] [WS-2] [WS-3] (WebSocket servers)
\ | /
[Redis Pub/Sub] (message backbone)
|
[App Servers] (business logic)
User A connected to WS-1 sends message to User B on WS-3:
1. WS-1 receives message from User A
2. WS-1 publishes to Redis channel for User B
3. Redis delivers to WS-3 (which subscribes to User B's channel)
4. WS-3 pushes the message to User B's WebSocket
Numbers That Matter
- A single server can handle 100K-1M concurrent WebSocket connections with proper tuning (Linux file descriptor limits, epoll)
- WebSocket frame overhead: 2-6 bytes vs HTTP header overhead: 200-800 bytes per request
- SSE reconnection default: browsers retry after ~3 seconds with no extra code
- Discord handles 6+ million concurrent WebSocket connections across their server fleet
- Phoenix (Elixir) has demonstrated 2 million concurrent WebSocket connections on a single server
Interview Tip
When designing any real-time feature in an interview, explicitly choose between polling, SSE, and WebSockets. Say: 'For notifications I would use SSE because it is server-to-client only and simpler to scale. For chat I would use WebSockets because both sides send messages. In both cases I need a Redis Pub/Sub layer to route messages across horizontally scaled servers.' This shows you understand both the protocol choice AND the scaling implications.
Key Takeaway
WebSockets give you bidirectional real-time communication. SSE gives you server-to-client streaming over plain HTTP. Choose based on whether the client needs to send data back on the same channel. Both require a pub/sub backbone (like Redis) for horizontal scaling, and both need careful connection lifecycle management at scale.