Long Polling vs SSE vs WebSocket: Complete Reference

Type: Software Reference Confidence: 0.92 Sources: 7 Verified: 2026-02-24 Freshness: 2026-02-24

TL;DR

Constraints

Quick Reference

FeatureShort PollingLong PollingSSEWebSocketWebTransport
DirectionClient-to-serverClient-to-serverServer-to-clientBidirectionalBidirectional
LatencyHigh (poll interval)Medium (held request)Low (~same as WS)Low (~3ms advantage)Lowest (UDP optional)
Per-message overheadFull HTTP headers (~800B)Full HTTP headers (~800B)~5 bytes/message~2 bytes/frame~2 bytes/frame
Auto-reconnectN/AManualBuilt-in (EventSource)ManualManual
Binary supportYes (via encoding)Yes (via encoding)No (UTF-8 text only)Yes (native)Yes (native + datagrams)
HTTP/2 multiplexingYesYesYes (removes 6-conn limit)No (separate TCP)Yes (QUIC streams)
Browser supportUniversalUniversal97%+ (no IE)98%+Chromium only (experimental)
Proxy friendlinessExcellentGoodExcellent (standard HTTP)Poor (needs upgrade)Poor (needs HTTP/3)
Scaling difficultyLow (stateless)High (held connections)Medium (long-lived HTTP)High (stateful connections)Medium (QUIC)
Connection setupNew HTTP request each timeNew HTTP request each timeSingle HTTP streamHTTP upgrade to TCPHTTP/3 CONNECT
Max connections/domainBrowser-limitedBrowser-limited6 (HTTP/1.1), unlimited (HTTP/2)Browser-limitedBrowser-limited
Ideal use caseDashboards with 30s+ intervalsLegacy compatibilityLive feeds, notificationsChat, gaming, collab editingFuture: gaming, streaming

Decision Tree

START
|-- Does data flow only from server to client?
|   |-- YES --> Is HTTP/2 available?
|   |   |-- YES --> Use SSE (EventSource API)
|   |   |-- NO --> Will you have >6 tabs open to same domain?
|   |       |-- YES --> Use WebSocket (to avoid 6-connection limit)
|   |       |-- NO --> Use SSE
|   |-- NO (bidirectional needed) --> Is sub-second latency critical?
|       |-- YES --> Are corporate proxies / firewalls a concern?
|       |   |-- YES --> Use WebSocket over WSS (port 443) with long-polling fallback
|       |   |-- NO --> Use WebSocket
|       |-- NO --> Can you use request-response for client-to-server?
|           |-- YES --> Use SSE for server push + regular HTTP POST for client messages
|           |-- NO --> Use WebSocket
|
|-- Must support IE11 or very old browsers?
|   |-- YES --> Use long polling (or polyfill EventSource)
|   |-- NO --> See above
|
|-- Need unreliable/unordered datagrams (gaming, media)?
    |-- YES --> Use WebTransport (with WebSocket fallback)
    |-- NO --> See above

Step-by-Step Guide

1. Implement an SSE server endpoint

Create a server endpoint that sets the correct headers and streams events. The Content-Type must be text/event-stream, and you must disable response buffering. [src1]

// Node.js (Express) SSE server
const express = require('express');
const app = express();

app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'X-Accel-Buffering': 'no'
  });
  res.write(':ok\n\n');

  const interval = setInterval(() => {
    const data = JSON.stringify({ time: new Date().toISOString() });
    res.write(`id:${Date.now()}\nevent:update\ndata:${data}\n\n`);
  }, 1000);

  req.on('close', () => { clearInterval(interval); res.end(); });
});

app.listen(3000);

Verify: curl -N http://localhost:3000/events → streaming event:update messages every second

2. Connect with EventSource on the client

The browser's built-in EventSource API handles reconnection automatically. If the connection drops, it reconnects after a server-specified retry: interval (default ~3 seconds). [src1]

const source = new EventSource('/events');

source.addEventListener('update', (e) => {
  const data = JSON.parse(e.data);
  console.log('Received:', data);
});

source.addEventListener('error', (e) => {
  if (source.readyState === EventSource.CONNECTING) {
    console.log('Reconnecting...');
  } else {
    console.error('SSE connection failed');
    source.close();
  }
});

Verify: Open browser DevTools Network tab → filter by EventSource → confirm streaming responses

3. Implement a WebSocket server

For bidirectional communication, set up a WebSocket server using the ws library for Node.js. [src6]

const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', (ws, req) => {
  ws.on('message', (message) => {
    const parsed = JSON.parse(message);
    wss.clients.forEach((client) => {
      if (client.readyState === 1) {
        client.send(JSON.stringify({ from: 'server', ...parsed }));
      }
    });
  });
  ws.on('close', (code, reason) => {
    console.log(`Disconnected: ${code} ${reason}`);
  });
  ws.send(JSON.stringify({ type: 'connected', time: Date.now() }));
});

Verify: npx wscat -c ws://localhost:8080 → type {"text":"hello"} → see broadcast response

4. Add client-side reconnection for WebSocket

Unlike SSE, WebSocket does not auto-reconnect. Implement reconnection with exponential backoff. [src4]

function createWebSocket(url) {
  let ws, retries = 0;
  const maxRetries = 10, baseDelay = 1000;

  function connect() {
    ws = new WebSocket(url);
    ws.onopen = () => { retries = 0; };
    ws.onmessage = (event) => { console.log(JSON.parse(event.data)); };
    ws.onclose = (event) => {
      if (!event.wasClean && retries < maxRetries) {
        const delay = Math.min(baseDelay * Math.pow(2, retries++), 30000);
        setTimeout(connect, delay);
      }
    };
  }
  connect();
  return { getSocket: () => ws };
}

Verify: Kill and restart the server → client reconnects automatically with increasing delays

Code Examples

Python: SSE Server with Flask

# Input:  HTTP GET /events
# Output: text/event-stream with JSON events

from flask import Flask, Response
import json, time

app = Flask(__name__)

def event_stream():
    while True:
        data = json.dumps({"time": time.time()})
        yield f"id:{int(time.time())}\nevent:update\ndata:{data}\n\n"
        time.sleep(1)

@app.route('/events')
def sse():
    return Response(
        event_stream(),
        mimetype='text/event-stream',
        headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'}
    )

if __name__ == '__main__':
    app.run(port=3000, threaded=True)

Python: WebSocket Server with websockets

# Input:  WebSocket connection on ws://localhost:8765
# Output: Echoes received messages back to all clients

import asyncio, websockets, json

connected = set()

async def handler(websocket):
    connected.add(websocket)
    try:
        async for message in websocket:
            data = json.loads(message)
            broadcast = json.dumps({"from": "server", **data})
            await asyncio.gather(
                *(c.send(broadcast) for c in connected if c.open)
            )
    finally:
        connected.discard(websocket)

async def main():
    async with websockets.serve(handler, "localhost", 8765):
        await asyncio.Future()

asyncio.run(main())

Go: SSE Server

// Input:  HTTP GET /events
// Output: text/event-stream with periodic updates

package main

import (
    "fmt"
    "net/http"
    "time"
)

func sseHandler(w http.ResponseWriter, r *http.Request) {
    flusher, ok := w.(http.Flusher)
    if !ok {
        http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")

    for {
        select {
        case <-r.Context().Done():
            return
        default:
            fmt.Fprintf(w, "id:%d\nevent:update\ndata:{\"time\":\"%s\"}\n\n",
                time.Now().UnixMilli(), time.Now().Format(time.RFC3339))
            flusher.Flush()
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    http.HandleFunc("/events", sseHandler)
    http.ListenAndServe(":3000", nil)
}

Anti-Patterns

Wrong: Using WebSocket for simple server-to-client notifications

// BAD -- WebSocket for one-way server push adds unnecessary complexity
const ws = new WebSocket('wss://api.example.com/notifications');
ws.onmessage = (e) => showNotification(JSON.parse(e.data));
// Now you need: reconnection logic, heartbeat pings, connection state
// management, proxy configuration, load balancer sticky sessions...

Correct: Use SSE for server-to-client push

// GOOD -- SSE handles reconnection automatically, works through all proxies
const source = new EventSource('/notifications');
source.onmessage = (e) => showNotification(JSON.parse(e.data));
// Auto-reconnects. Works through corporate proxies. No library needed.

Wrong: Short polling for real-time chat

// BAD -- polling every 500ms wastes bandwidth and battery
setInterval(async () => {
  const res = await fetch('/api/messages?since=' + lastTimestamp);
  const messages = await res.json();
  messages.forEach(displayMessage);
}, 500);
// 172,800 requests/day per client. Most return empty arrays.

Correct: WebSocket for bidirectional real-time messaging

// GOOD -- single persistent connection, instant delivery
const ws = new WebSocket('wss://chat.example.com/ws');
ws.onmessage = (e) => displayMessage(JSON.parse(e.data));

function sendMessage(text) {
  ws.send(JSON.stringify({ type: 'message', text }));
}
// One connection. Near-zero latency. Minimal bandwidth.

Wrong: No reconnection logic on WebSocket

// BAD -- connection drops silently, user sees stale data
const ws = new WebSocket('wss://example.com/ws');
ws.onmessage = handleMessage;
// Network blip? Connection gone. No recovery. User confused.

Correct: WebSocket with exponential backoff reconnection

// GOOD -- automatic reconnection with backoff
function connect(url, onMessage) {
  let retries = 0;
  function attempt() {
    const ws = new WebSocket(url);
    ws.onopen = () => { retries = 0; };
    ws.onmessage = onMessage;
    ws.onclose = (e) => {
      if (!e.wasClean) {
        const delay = Math.min(1000 * 2 ** retries++, 30000);
        setTimeout(attempt, delay);
      }
    };
  }
  attempt();
}

Common Pitfalls

Diagnostic Commands

# Test SSE endpoint with curl (streaming)
curl -N -H "Accept: text/event-stream" http://localhost:3000/events

# Test WebSocket with wscat
npx wscat -c ws://localhost:8080

# Check if WebSocket upgrade is working through Nginx
curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  http://localhost/ws
# Expected: HTTP/1.1 101 Switching Protocols

# Monitor open SSE/WebSocket connections on the server
ss -tn state established | grep :3000 | wc -l

# Check HTTP/2 support (required for SSE without 6-conn limit)
curl -I --http2 https://example.com 2>&1 | grep -i "http/2"

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Server pushes updates to browser (dashboards, feeds, alerts)Client needs to send frequent messages to serverWebSocket
You want zero-library client code with auto-reconnectYou need binary data transfer (images, audio, files)WebSocket
Running behind corporate proxies or strict firewallsBuilding real-time multiplayer gamingWebSocket or WebTransport
HTTP/2 is available and you have many concurrent streamsIE11 support required and polyfills not acceptableLong polling
Building chat, collaborative editing, or live gamingData updates every 30+ seconds with no urgencyShort polling (simpler)
Both client and server send messages frequentlyProxy/firewall blocks WebSocket and WSS fallback failsSSE + HTTP POST
Legacy browser/proxy environment blocks SSE and WebSocketLatency and efficiency matterSSE or WebSocket (with polyfill)

Important Caveats

Related Units