SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

System DesignWebSockets & Long Polling
System Designbeginner

WebSockets & Long Polling

How to achieve real-time, bidirectional communication over HTTP — understand the evolution from short polling to long polling, Server-Sent Events, and WebSockets, with a runnable Go chat server implementation.

January 3, 202413 min read
websocketslong-pollingssereal-timebuilding-blocktier-0

Metadata

FieldValue
Slugwebsockets-long-polling
DifficultyBeginner
Estimated Reading Time12 min
Estimated Coding Time25 min
Tier0 — Networking Fundamentals
Implementation LanguageGo
SEO DescriptionMaster 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

  1. 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.

  2. 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.

  3. Server-Sent Events / SSE (2006): Client opens one HTTP connection. Server streams events down text/event-stream. Problem: Unidirectional (Server → Client only).

  4. 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

SystemProtocolWhy?
WhatsApp WebWebSocketsBidirectional chat, presence updates
Twitter Live UpdatesLong Polling / SSEMostly unidirectional (Server → Client)
Uber (Driver Tracking)WebSockets / SSEHigh-frequency location updates
Robinhood / BinanceWebSocketsHigh-frequency stock/crypto ticks
Google DocsWebSocketsBidirectional operational transformation
ChatGPT (UI)SSEStreaming the generated response tokens
TrelloWebSocketsReal-time board updates

4. Prerequisites

ConceptBlock
HTTP Request/Response flow001 HTTP & TCP/IP Fundamentals
REST API constraints002 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

  1. Client makes an HTTP request.
  2. 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).
  3. When data arrives, the server wakes up the request, writes the data, and closes the connection.
  4. Client processes the data and immediately fires another HTTP request.
  5. 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

  1. Client makes an HTTP request with Accept: text/event-stream.
  2. Server responds with 200 OK, Content-Type: text/event-stream, but does not close the connection.
  3. Server writes data as text lines: data: {"msg": "hello"}\n\n.
  4. Client's browser (via EventSource API) 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:

  • FIN bit: Is this the last fragment of the message?
  • Opcode: Text (1), Binary (2), Close (8), Ping (9), Pong (10).
  • Mask bit & 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

MetricWebSocketsLong PollingSSE
Connection Setup1 TCP + 1 HTTP (Upgrade)1 HTTP per message1 HTTP
Overhead per Msg~2-10 bytes (binary frame)~800 bytes (HTTP headers)~20 bytes (data: prefix)
Memory per ClientHigh (dedicated socket/goroutine)Medium (request object)High (dedicated socket)
LatencyExtremely 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

AspectWebSocketsLong PollingSSE
DirectionalityFull Duplex (Bidirectional)Half DuplexUnidirectional (Server→Client)
Header OverheadMinimalHighMedium
Proxy/CDN SupportPoor (Proxies often drop idle TCP)Great (Standard HTTP)Good (Standard HTTP)
Auto-Reconnect❌ Manual✅ Handled by client loop✅ Built-in to browser
Mobile BatteryBetterWorse (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

ConcernThis ImplementationProduction (e.g., Pusher, WhatsApp)
BroadcastingIn-memory channelRedis Pub/Sub (Block 024) across multiple servers
Connection StateLocal mapDistributed presence system
Dropped ConnectionsAssumes clean closeHeartbeats (Ping/Pong frames) to detect dead clients
Thundering HerdNoneJitter in client reconnect logic
SecurityOpenAuth token passed during initial HTTP handshake
Message OrderingImplicitSequence numbers + client-side reordering
Missed MessagesLost foreverOffline message queue + Sync on reconnect

11. Common Bugs

BugWhat happensFix
Nginx drops idle connectionsWS connection closes after 60s of silenceImplement Ping/Pong heartbeats every 30s
Concurrent Map Writes (Go)Fatal crash when two clients connect/disconnectAlways use sync.Mutex or sync.RWMutex around connection maps
Thundering HerdServer deploys → 1M clients reconnect exactly at 0.0s → Server crashesAdd random jitter (e.g., sleep(random(10, 5000) ms)) before reconnecting
Authenticating via WS frameFirst WS frame is auth, but connection is already upgradedAuthenticate via query param or cookie during the HTTP Upgrade GET
Memory LeakClient drops network, TCP doesn't noticeRead timeouts + Ping/Pong

12. Interview Questions

  1. 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.

  2. 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.

  3. 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).

  4. 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.

  5. 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)

SystemProtocol Strategy
WhatsAppWebSockets (via Erlang/XMPP) for instant delivery
UberWebSockets/SSE for live car location tracking
Twitter/XSSE for live tweet counts
InstagramWebSockets for Instagram Live comments
TinyURLNot used
NetflixNot typically used for streaming (uses chunked HTTP), but used for second-screen control
Google DriveWebSockets for collaborative editing (OT/CRDT)

15. Related Blocks

RelationshipBlock
Previous001 HTTP & TCP/IP
Previous002 REST API Design
Next024 Pub/Sub Systems (to scale this)
AlternativegRPC 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

FieldValue
Hero TitleWebSockets & Long Polling
Hero SubtitleBidirectional, real-time communication — how the web evolved from refreshing pages to instant chat
BreadcrumbSystem Design → Building Blocks → WebSockets
Sidebar CategoryTier 0 — Networking Fundamentals
Search Keywordswebsockets, 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 IllustrationA tennis match: REST is a ball machine (one way), WebSockets is two players hitting the ball back and forth
Suggested AnimationComparison animation: Short polling (constant failed requests) vs Long polling (held requests) vs WebSockets (persistent pipe with messages flowing both ways)
PreviousDNS & Service DiscoveryNextREST API Design