Metadata
| Field | Value |
|---|---|
| Slug | websockets-long-polling |
| Difficulty | Beginner |
| Estimated Reading Time | 12 min |
| Estimated Coding Time | 25 min |
| Tier | 0 — Networking Fundamentals |
| Implementation Language | Go |
| SEO Description | Master real-time communication protocols — learn how Long Polling, Server-Sent Events (SSE), and WebSockets work internally, with a Go chat server implementation. |
1. Overview
What problem does it solve?
HTTP is strictly unidirectional and request-driven: the client must ask for data, and the server can only respond. If the server has new data (like a chat message or a stock price update), it cannot push it to the client. WebSockets and Long Polling solve the real-time push problem, enabling bidirectional or server-to-client communication over the web.
What breaks without it?
Without real-time protocols:
- Chat apps (WhatsApp) would require users to constantly refresh to see new messages.
- Trading platforms would show stale stock prices.
- Collaborative tools (Google Docs) couldn't show other users typing.
- Live dashboards would be delayed.
- Systems would be DDoS'd by clients aggressively polling for updates.
2. Motivation
The Evolution of Real-Time Web
-
Short Polling (The Naive Way): Client asks "Any updates?" every 1 second. Problem: 99% of requests return "No updates", wasting bandwidth, battery, and server CPU.
-
Long Polling (The Hack, 2000s): Client asks "Any updates?". Server holds the request open until it has an update, then responds. Client immediately opens a new request. Problem: Still carries HTTP header overhead per message; connection setup/teardown latency.
-
Server-Sent Events / SSE (2006): Client opens one HTTP connection. Server streams events down
text/event-stream. Problem: Unidirectional (Server → Client only). -
WebSockets (2011): Client and server upgrade an HTTP connection into a persistent, raw TCP-like connection. Both can send data at any time. Problem: Harder to load balance; proxies often drop idle connections.
3. Real-World Usage
| System | Protocol | Why? |
|---|---|---|
| WhatsApp Web | WebSockets | Bidirectional chat, presence updates |
| Twitter Live Updates | Long Polling / SSE | Mostly unidirectional (Server → Client) |
| Uber (Driver Tracking) | WebSockets / SSE | High-frequency location updates |
| Robinhood / Binance | WebSockets | High-frequency stock/crypto ticks |
| Google Docs | WebSockets | Bidirectional operational transformation |
| ChatGPT (UI) | SSE | Streaming the generated response tokens |
| Trello | WebSockets | Real-time board updates |
4. Prerequisites
| Concept | Block |
|---|---|
| HTTP Request/Response flow | 001 HTTP & TCP/IP Fundamentals |
| REST API constraints | 002 REST API Design |
5. Visual Explanation
Polling vs Long Polling vs WebSockets
sequenceDiagram
participant C as Client
participant S as Server
rect rgb(240, 248, 255)
Note over C,S: 1. Short Polling (High Overhead)
C->>S: HTTP GET /updates
S-->>C: 200 OK (Empty)
Note right of C: Wait 1s
C->>S: HTTP GET /updates
S-->>C: 200 OK (Empty)
Note right of C: Wait 1s
C->>S: HTTP GET /updates
S-->>C: 200 OK (New Message)
end
rect rgb(240, 255, 240)
Note over C,S: 2. Long Polling (Lower Overhead)
C->>S: HTTP GET /updates
Note over S: Server holds connection open...
Note over S: ...until message arrives
S-->>C: 200 OK (New Message)
C->>S: HTTP GET /updates (reconnect immediately)
end
rect rgb(255, 240, 240)
Note over C,S: 3. WebSockets (Bidirectional, Persistent)
C->>S: HTTP GET (Connection: Upgrade, Upgrade: websocket)
S-->>C: HTTP 101 Switching Protocols
Note over C,S: Connection is now a persistent TCP socket
S->>C: WS Frame: Message A
C->>S: WS Frame: Message B
S->>C: WS Frame: Message C
end
The WebSocket Upgrade Handshake
Every WebSocket connection starts its life as a standard HTTP GET request:
Client sends:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Server responds:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
After the 101 status code, the connection is no longer HTTP. It's raw WebSocket frames over TCP.
6. Internal Working
6.1 Long Polling Internals
- Client makes an HTTP request.
- Server sees no data is available. Instead of returning empty, it puts the request in a "waiting" state (usually blocking a goroutine or suspending in an event loop).
- When data arrives, the server wakes up the request, writes the data, and closes the connection.
- Client processes the data and immediately fires another HTTP request.
- Handling Timeouts: If no data arrives for ~30 seconds, the server returns empty, and the client reconnects (to prevent proxies from killing idle connections).
6.2 Server-Sent Events (SSE) Internals
- Client makes an HTTP request with
Accept: text/event-stream. - Server responds with
200 OK,Content-Type: text/event-stream, but does not close the connection. - Server writes data as text lines:
data: {"msg": "hello"}\n\n. - Client's browser (via
EventSourceAPI) parses this stream.
6.3 WebSocket Data Framing
Once upgraded, WebSockets don't use HTTP headers. They use a binary framing format to minimize overhead (2–10 bytes per frame vs hundreds of bytes for HTTP headers).
A frame contains:
FINbit: Is this the last fragment of the message?Opcode: Text (1), Binary (2), Close (8), Ping (9), Pong (10).Maskbit & Masking Key: Prevents proxy cache poisoning.Payload Length: How big is the data.Payload Data: The actual message.
7. Implementation
Why Go? Go's lightweight goroutines make it the absolute best language for handling thousands of long-lived, concurrent connections. We can dedicate two goroutines (one for reading, one for writing) to every single WebSocket connection without running out of memory.
/*
003 - Real-Time Server
Demonstrates WebSockets and Long Polling in Go.
Run: `go run server.go`
*/
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"sync"
"time"
"github.com/gorilla/websocket"
)
// ── Shared Domain Logic ──
type Message struct {
User string `json:"user"`
Text string `json:"text"`
Time int64 `json:"time"`
}
var (
// A channel to broadcast messages to all connected clients
broadcast = make(chan Message)
)
// ── 1. WebSocket Implementation ──
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, // Allow all CORS for demo
}
// Active WebSocket connections
var wsClients = make(map[*websocket.Conn]bool)
var wsMutex sync.Mutex
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
// Upgrade HTTP GET to WebSocket
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade error:", err)
return
}
defer conn.Close()
// Register client
wsMutex.Lock()
wsClients[conn] = true
wsMutex.Unlock()
log.Printf("WS Client Connected: %s", conn.RemoteAddr())
// Read loop (receive messages from this client)
for {
var msg Message
err := conn.ReadJSON(&msg)
if err != nil {
log.Printf("WS Client Disconnected: %s", conn.RemoteAddr())
wsMutex.Lock()
delete(wsClients, conn)
wsMutex.Unlock()
break
}
msg.Time = time.Now().UnixMilli()
// Send to global broadcast channel
broadcast <- msg
}
}
// Background worker that pushes broadcast messages to all WS clients
func handleMessages() {
for {
// Wait for a new message
msg := <-broadcast
wsMutex.Lock()
for client := range wsClients {
err := client.WriteJSON(msg)
if err != nil {
client.Close()
delete(wsClients, client)
}
}
wsMutex.Unlock()
}
}
// ── 2. Long Polling Implementation ──
// Clients waiting for a long poll response
var lpWaiters []chan Message
var lpMutex sync.Mutex
func handleLongPoll(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
// Client sending a message
var msg Message
json.NewDecoder(r.Body).Decode(&msg)
msg.Time = time.Now().UnixMilli()
// Broadcast to WS and LP clients
broadcast <- msg
// Also wake up LP waiters directly
lpMutex.Lock()
waiters := lpWaiters
lpWaiters = nil // Clear list
lpMutex.Unlock()
for _, waiter := range waiters {
waiter <- msg
}
w.WriteHeader(http.StatusCreated)
return
}
// Client waiting for a message (HTTP GET)
waitChan := make(chan Message, 1)
lpMutex.Lock()
lpWaiters = append(lpWaiters, waitChan)
lpMutex.Unlock()
// Wait for a message OR a 30-second timeout
select {
case msg := <-waitChan:
// Message arrived! Respond immediately.
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(msg)
case <-time.After(30 * time.Second):
// Timeout. Tell client to reconnect.
w.WriteHeader(http.StatusNoContent)
// Clean up waiter
lpMutex.Lock()
for i, w := range lpWaiters {
if w == waitChan {
lpWaiters = append(lpWaiters[:i], lpWaiters[i+1:]...)
break
}
}
lpMutex.Unlock()
}
}
func main() {
// Start the WS broadcast worker
go handleMessages()
http.HandleFunc("/ws", handleWebSocket)
http.HandleFunc("/longpoll", handleLongPoll)
fmt.Println("Real-time Server running on :8080")
fmt.Println("WebSocket endpoint: ws://localhost:8080/ws")
fmt.Println("Long Poll endpoint: http://localhost:8080/longpoll")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Sample Output
# Terminal 1: Start Server
$ go get github.com/gorilla/websocket
$ go run server.go
Real-time Server running on :8080
# Terminal 2: Connect a WebSocket client (using wscat tool)
$ wscat -c ws://localhost:8080/ws
> {"user": "Alice", "text": "Hello WS world!"}
< {"user":"Alice","text":"Hello WS world!","time":1698765432100}
# Terminal 3: Test Long Polling
# This will BLOCK until someone sends a message...
$ curl http://localhost:8080/longpoll
# Terminal 4: Send a message via Long Poll POST
$ curl -X POST http://localhost:8080/longpoll -d '{"user":"Bob", "text":"Hi from curl"}'
# ... Terminal 3 immediately unblocks and outputs:
{"user":"Bob","text":"Hi from curl","time":1698765445200}
# ... Terminal 2 (WebSocket) also receives:
< {"user":"Bob","text":"Hi from curl","time":1698765445200}
8. Complexity
| Metric | WebSockets | Long Polling | SSE |
|---|---|---|---|
| Connection Setup | 1 TCP + 1 HTTP (Upgrade) | 1 HTTP per message | 1 HTTP |
| Overhead per Msg | ~2-10 bytes (binary frame) | ~800 bytes (HTTP headers) | ~20 bytes (data: prefix) |
| Memory per Client | High (dedicated socket/goroutine) | Medium (request object) | High (dedicated socket) |
| Latency | Extremely Low (<5ms) | Medium (10-50ms setup) | Low |
Scalability Characteristics
- The C10K / C1M Problem: Handling 1 million concurrent WebSockets means holding 1 million open TCP sockets. Operating systems impose limits on open file descriptors (
ulimit -n). - Load Balancing Challenges: Because WS connections are persistent, standard round-robin load balancers can result in uneven distribution. You need "sticky sessions" or consistent hashing.
- Statefulness: Since the server holds the connection, if the server restarts, all clients disconnect and reconnect simultaneously, causing a "Thundering Herd" problem.
9. Trade-offs
| Aspect | WebSockets | Long Polling | SSE |
|---|---|---|---|
| Directionality | Full Duplex (Bidirectional) | Half Duplex | Unidirectional (Server→Client) |
| Header Overhead | Minimal | High | Medium |
| Proxy/CDN Support | Poor (Proxies often drop idle TCP) | Great (Standard HTTP) | Good (Standard HTTP) |
| Auto-Reconnect | ❌ Manual | ✅ Handled by client loop | ✅ Built-in to browser |
| Mobile Battery | Better | Worse (wakes radio often) | Better |
When to use what?
- WebSockets: Gaming, chat apps, collaborative editing, high-frequency trading.
- SSE: Live news feeds, LLM token streaming (ChatGPT), live scoreboards.
- Long Polling: Environments with strict corporate firewalls that block WS/SSE, or fallback mechanisms.
10. Production Evolution
| Concern | This Implementation | Production (e.g., Pusher, WhatsApp) |
|---|---|---|
| Broadcasting | In-memory channel | Redis Pub/Sub (Block 024) across multiple servers |
| Connection State | Local map | Distributed presence system |
| Dropped Connections | Assumes clean close | Heartbeats (Ping/Pong frames) to detect dead clients |
| Thundering Herd | None | Jitter in client reconnect logic |
| Security | Open | Auth token passed during initial HTTP handshake |
| Message Ordering | Implicit | Sequence numbers + client-side reordering |
| Missed Messages | Lost forever | Offline message queue + Sync on reconnect |
11. Common Bugs
| Bug | What happens | Fix |
|---|---|---|
| Nginx drops idle connections | WS connection closes after 60s of silence | Implement Ping/Pong heartbeats every 30s |
| Concurrent Map Writes (Go) | Fatal crash when two clients connect/disconnect | Always use sync.Mutex or sync.RWMutex around connection maps |
| Thundering Herd | Server deploys → 1M clients reconnect exactly at 0.0s → Server crashes | Add random jitter (e.g., sleep(random(10, 5000) ms)) before reconnecting |
| Authenticating via WS frame | First WS frame is auth, but connection is already upgraded | Authenticate via query param or cookie during the HTTP Upgrade GET |
| Memory Leak | Client drops network, TCP doesn't notice | Read timeouts + Ping/Pong |
12. Interview Questions
-
Why does WhatsApp use WebSockets instead of REST? Hint: REST is client-driven. Server needs a persistent channel to push messages to the client instantly.
-
If you have 5 backend servers, how do you broadcast a chat message to a user connected to Server 3 if the sender is on Server 1? Hint: You need a Pub/Sub system (like Redis) acting as a message bus between the servers.
-
Why do WebSocket connections often drop behind corporate proxies, and how do you fix it? Hint: Proxies kill idle TCP connections. Send frequent Ping/Pong frames (Heartbeats).
-
How do Server-Sent Events differ from WebSockets? When would you use SSE? Hint: SSE is unidirectional and uses standard HTTP. Great for ChatGPT token streaming or live feeds.
-
Explain the "Thundering Herd" problem in the context of WebSockets. Hint: Server restarts → 100K clients disconnect → 100K clients reconnect at the exact same millisecond. Use exponential backoff + jitter.
13. Used By (Downstream Blocks)
- 023 Message Queues — backend infra that powers real-time updates
- 024 Pub/Sub Systems — scales WS across multiple servers
- 027 Heartbeat & Failure Detection — ping/pong mechanisms
- 008 API Gateway — must be configured to support WebSocket upgrades
14. Used In (Case Studies)
| System | Protocol Strategy |
|---|---|
| WebSockets (via Erlang/XMPP) for instant delivery | |
| Uber | WebSockets/SSE for live car location tracking |
| Twitter/X | SSE for live tweet counts |
| WebSockets for Instagram Live comments | |
| TinyURL | Not used |
| Netflix | Not typically used for streaming (uses chunked HTTP), but used for second-screen control |
| Google Drive | WebSockets for collaborative editing (OT/CRDT) |
15. Related Blocks
| Relationship | Block |
|---|---|
| Previous | 001 HTTP & TCP/IP |
| Previous | 002 REST API Design |
| Next | 024 Pub/Sub Systems (to scale this) |
| Alternative | gRPC Streams |
16. Try It Yourself
Exercise 1: Implement Ping/Pong
Modify the Go server to send a WebSocket PingMessage every 20 seconds. If the client doesn't respond with a PongMessage within 5 seconds, close the connection.
Exercise 2: Redis Pub/Sub Integration
Install Redis. Modify the broadcast channel logic so that when a message is received from a client, it is published to a Redis channel. Have a separate goroutine subscribe to that Redis channel and push to wsClients. You now have a horizontally scalable chat server!
Website Metadata
| Field | Value |
|---|---|
| Hero Title | WebSockets & Long Polling |
| Hero Subtitle | Bidirectional, real-time communication — how the web evolved from refreshing pages to instant chat |
| Breadcrumb | System Design → Building Blocks → WebSockets |
| Sidebar Category | Tier 0 — Networking Fundamentals |
| Search Keywords | websockets, long polling, sse, server sent events, real-time web, chat architecture, bidirectional, persistent connections |
| Internal Links | ← 001 HTTP & TCP/IP · → 024 Pub/Sub Systems |
| Suggested Illustration | A tennis match: REST is a ball machine (one way), WebSockets is two players hitting the ball back and forth |
| Suggested Animation | Comparison animation: Short polling (constant failed requests) vs Long polling (held requests) vs WebSockets (persistent pipe with messages flowing both ways) |