WebSocket vs SSE vs Polling - Complete Deep Dive

Prerequisites: Load Balancing, API Design Used in: Chat System, Uber, Zomato, Stock Broker


What is Real-Time Communication?

Real-time communication is getting data from server to client as soon as it’s available, without the client explicitly asking for it.

Real-world analogy: Imagine waiting for a package delivery.


The Four Approaches

1. Short Polling (Regular Polling)

Client repeatedly asks the server at fixed intervals.

Client                              Server
  β”‚                                   β”‚
  │── GET /messages?since=100 ───────▢│
  │◀── 200 OK (no new messages) ─────│  ← Wasted request
  β”‚                                   β”‚
  β”‚    ... wait 5 seconds ...         β”‚
  β”‚                                   β”‚
  │── GET /messages?since=100 ───────▢│
  │◀── 200 OK (no new messages) ─────│  ← Wasted request
  β”‚                                   β”‚
  β”‚    ... wait 5 seconds ...         β”‚
  β”‚                                   β”‚
  │── GET /messages?since=100 ───────▢│
  │◀── 200 OK [{msg: "hello"}] ──────│  ← Finally got data!
  β”‚                                   β”‚

Problem: Most requests return empty. Wastes bandwidth and server resources. Delay = up to polling interval.


2. Long Polling

Client sends request, server holds it open until data is available (or timeout).

Client                              Server
  β”‚                                   β”‚
  │── GET /messages?since=100 ───────▢│
  β”‚                                   β”‚  Server holds connection
  β”‚         ... waiting ...           β”‚  open (30s timeout)
  β”‚                                   β”‚
  β”‚                                   β”‚  New message arrives!
  │◀── 200 OK [{msg: "hello"}] ──────│
  β”‚                                   β”‚
  │── GET /messages?since=101 ───────▢│  ← Immediately reconnect
  β”‚                                   β”‚  Server holds again...
  β”‚         ... waiting ...           β”‚

Improvement over polling: No wasted requests. Data arrives as soon as available. But each response requires a new connection.


3. Server-Sent Events (SSE)

Server pushes data to client over a single long-lived HTTP connection. One-way only (server β†’ client).

Client                              Server
  β”‚                                   β”‚
  │── GET /events (Accept: text/     β”‚
  β”‚   event-stream) ────────────────▢│
  β”‚                                   β”‚
  │◀── data: {"msg": "hello"} ───────│  Push 1
  β”‚                                   β”‚
  │◀── data: {"msg": "world"} ───────│  Push 2
  β”‚                                   β”‚
  │◀── data: {"typing": true} ───────│  Push 3
  β”‚                                   β”‚
  β”‚    (connection stays open         β”‚
  β”‚     indefinitely)                 β”‚
// Client-side (browser)
const evtSource = new EventSource('/events/notifications');

evtSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  showNotification(data);
};

// Auto-reconnects if connection drops!

Key feature: Built-in browser reconnection. Uses standard HTTP (works through proxies, CDNs). One-way only.


4. WebSocket

Full-duplex, bidirectional communication over a single TCP connection.

Client                              Server
  β”‚                                   β”‚
  │── HTTP Upgrade: websocket ───────▢│  Handshake
  │◀── 101 Switching Protocols ──────│
  β”‚                                   β”‚
  │═══ WebSocket Connection ═══════════  (persistent TCP)
  β”‚                                   β”‚
  │──▢ {"type": "send_msg",          β”‚  Client β†’ Server
  β”‚      "text": "hello"}            β”‚
  β”‚                                   β”‚
  │◀── {"type": "new_msg",           β”‚  Server β†’ Client
  β”‚      "from": "Bob",              β”‚
  β”‚      "text": "hey!"}             β”‚
  β”‚                                   β”‚
  │◀── {"type": "typing",            β”‚  Server β†’ Client
  β”‚      "user": "Bob"}              β”‚
  β”‚                                   β”‚
  │──▢ {"type": "read_receipt",      β”‚  Client β†’ Server
  β”‚      "msgId": "abc"}             β”‚

Comparison Table

Feature Short Polling Long Polling SSE WebSocket
Direction Client β†’ Server Client β†’ Server Server β†’ Client Bidirectional
Latency Up to interval Near real-time Real-time Real-time
Connection overhead New conn each poll New conn each response Single long-lived Single long-lived
Browser support All All All modern (no IE) All modern
HTTP compatible Yes Yes Yes No (upgrades to WS)
Works through proxies Yes Sometimes issues Yes Sometimes issues
Auto-reconnect N/A Manual Built-in Manual
Server resource usage Low per request Medium (held conns) Low Medium
Scalability Easy Medium Easy Hard
Max connections N/A Limited ~6 per domain ~65K per server
Binary data No No No (text only) Yes

Scaling WebSockets

The hardest challenge in real-time systems. Here’s why and how:

The Problem

Server A has WebSocket to User 1
Server B has WebSocket to User 2

User 1 sends message to User 2.
Server A receives it... but User 2 is on Server B!

How does Server A forward to Server B?

Solution: Pub/Sub Layer

flowchart TD
    A["Server A<br/>Users: 1 3 5"] --> D["Redis Pub/Sub<br/>or Kafka or NATS"]
    B["Server B<br/>Users: 2 4 6"] --> D
    C["Server C<br/>Users: 7 8 9"] --> D
    D --> A
    D --> B
    D --> C

    classDef service fill:#10b981,stroke:#065f46,color:#fff
    classDef async fill:#818cf8,stroke:#4338ca,color:#fff
    class A,B,C service
    class D async

Flow:

  1. User 1 sends msg to User 2 via WebSocket on Server A
  2. Server A publishes to Redis channel β€œuser:2”
  3. Server B (subscribed to β€œuser:2”) receives it
  4. Server B pushes to User 2’s WebSocket

Connection Management

Sticky Sessions (Load Balancer):
  - Once a WebSocket is established, all traffic goes to same server
  - Use IP hash or cookie-based routing
  - Problem: uneven load if some users are chatty

Connection Registry (Redis):
  - Store mapping: userId β†’ serverId
  - When message arrives, look up which server holds the connection
  - Route message to correct server via pub/sub

Heartbeats and Reconnection

Client                              Server
  β”‚                                   β”‚
  │◀── PING ─────────────────────────│  Every 30s
  │──▢ PONG ─────────────────────────│
  β”‚                                   β”‚
  │◀── PING ─────────────────────────│
  β”‚    (no PONG in 10s)              β”‚
  β”‚    Connection considered dead     β”‚
  β”‚    Clean up resources             β”‚

When to Use What

Use Case Best Choice Why
Chat application WebSocket Bidirectional, low latency, typing indicators
Live sports scores SSE Server push only, auto-reconnect, simple
Stock ticker WebSocket High-frequency updates, bidirectional (subscribe/unsubscribe)
Email inbox Long Polling Infrequent updates, simpler infra
Social media feed refresh Short Polling Low frequency, simple, cacheable
Ride tracking (Uber) WebSocket or SSE Continuous location updates
Notifications SSE One-way server push, reliable
Online gaming WebSocket Low latency, bidirectional, binary data
IoT sensor data WebSocket High throughput, bidirectional control
Dashboard metrics SSE Server push, auto-reconnect

When NOT to Use WebSockets


Real-World Examples

Company Technology Use Case
Slack WebSocket Real-time messaging, typing indicators, presence
Uber WebSocket + gRPC streaming Driver location tracking, ride status
Discord WebSocket Voice + text chat, presence, real-time events
Robinhood WebSocket Real-time stock price updates
Twitter SSE (Streaming API) Real-time tweet delivery to clients
Facebook Long Polling (MQTT internally) Chat, notifications
Twitch WebSocket (IRC protocol) Chat messages during streams

Common Interview Questions

Q: β€œHow would you implement real-time messaging in a chat app?” A: WebSocket for bidirectional communication. Client establishes WS connection on app open. Messages sent via WS to server, which stores in DB and forwards via Pub/Sub (Redis) to the recipient’s WS server. Use heartbeats for connection health. On disconnect, queue messages and deliver on reconnect.

Q: β€œHow do you scale WebSocket servers to millions of users?” A: Multiple WS servers behind a load balancer with sticky sessions. Add Redis Pub/Sub (or Kafka) as a message bus between servers. Store connection registry (userId β†’ serverId) in Redis. Each server subscribes to channels for its connected users. Horizontally scale by adding more WS servers.

Q: β€œWhat happens when a WebSocket connection drops?” A: Client-side: auto-reconnect with exponential backoff. Server-side: heartbeat timeout triggers cleanup. On reconnect, client sends last received message ID, server replays missed messages from a short-term buffer (Redis sorted set or DB). Design for at-least-once delivery with client-side deduplication.

Q: β€œWhy not just use WebSocket for everything?” A: WebSocket adds complexity (connection management, scaling, sticky sessions). For one-way push, SSE is simpler and works through CDNs/proxies. For infrequent data, polling is cheapest. WebSocket shines only when you need low-latency bidirectional communication. Over-engineering with WebSocket when polling suffices wastes engineering effort.

Q: β€œHow would you handle 1 million concurrent WebSocket connections?” A: Each server handles ~50-100K connections (with proper tuning). Need 10-20 servers. Use consistent hashing to distribute users. Redis Pub/Sub or Kafka for cross-server communication. Monitor connection counts per server. Auto-scale based on connection count metrics. Consider regional deployment to reduce latency.


← Back to Fundamentals Next: Idempotency β†’

Free system design + DSA prep. If it helped you crack an interview, consider supporting.