---
# === IDENTITY ===
id: software/patterns/polling-sse-websocket/2026
canonical_question: "When should I use long polling vs SSE vs WebSocket?"
aliases:
  - "long polling vs SSE vs WebSocket"
  - "Server-Sent Events vs WebSocket comparison"
  - "real-time communication protocol choice"
  - "EventSource vs WebSocket when to use"
  - "SSE or WebSocket for server push"
  - "short polling long polling SSE WebSocket WebTransport"
entity_type: software_reference
domain: software > patterns > long polling vs SSE vs WebSocket
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-24
confidence: 0.92
version: 1.0
first_published: 2026-02-24

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "WebTransport API still experimental (W3C Working Draft 2026-02-04); core protocols (SSE, WebSocket) stable since RFC 6455 (2011) and HTML5 spec"
  next_review: 2026-08-23
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "SSE only supports server-to-client communication; if you need bidirectional messaging, use WebSocket"
  - "WebSocket connections through corporate proxies often fail unless upgraded to WSS (TLS on port 443)"
  - "SSE is limited to 6 concurrent connections per domain in HTTP/1.1 browsers; use HTTP/2 to remove this limit"
  - "WebTransport requires HTTP/3 and is not production-ready as of 2026; do not use for production traffic without fallback"
  - "Long polling must include timeout handling and reconnection logic; a hung request without timeout leaks server resources"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need to implement webhook delivery (server-to-server, not server-to-browser)"
    use_instead: "software/system-design/webhooks-system/2026"
  - condition: "Need to design a full chat/messaging system architecture"
    use_instead: "software/system-design/chat-system/2026"
  - condition: "Need peer-to-peer real-time communication (video/audio)"
    use_instead: "software/patterns/webrtc-peer-connection/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: direction
    question: "Does data flow only from server to client, or do both sides send messages?"
    type: choice
    options: ["server-to-client only", "bidirectional"]
  - key: frequency
    question: "How frequently do updates arrive?"
    type: choice
    options: ["every few seconds", "sub-second / real-time", "irregular / event-driven"]
  - key: protocol_constraints
    question: "Are there network constraints to consider?"
    type: choice
    options: ["no constraints", "HTTP/2 not available", "corporate proxy / firewall", "must work on legacy browsers"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/patterns/polling-sse-websocket/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-24)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/system-design/webhooks-system/2026"
      label: "Webhook Delivery System Design"
    - id: "software/system-design/notification-system/2026"
      label: "Notification System Architecture"
    - id: "software/system-design/message-queue-event-driven/2026"
      label: "Message Queue & Event-Driven Architecture"
  solves:
    - id: "software/debugging/websocket-connection-failures/2026"
      label: "Debugging WebSocket Connection Failures"
  alternative_to: []
  often_confused_with:
    - id: "software/patterns/webrtc-peer-connection/2026"
      label: "WebRTC Peer-to-Peer Communication"

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "Using server-sent events"
    author: MDN Web Docs
    url: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
    type: official_docs
    published: 2025-12-15
    reliability: authoritative
  - id: src2
    title: "WebSockets vs Server-Sent-Events vs Long-Polling vs WebRTC vs WebTransport"
    author: RxDB
    url: https://rxdb.info/articles/websockets-sse-polling-webrtc-webtransport.html
    type: technical_blog
    published: 2025-08-10
    reliability: high
  - id: src3
    title: "WebSocket vs. Server-sent Events: A Performance Comparison"
    author: Timeplus
    url: https://www.timeplus.com/post/websocket-vs-sse
    type: technical_blog
    published: 2025-06-20
    reliability: high
  - id: src4
    title: "WebSockets vs SSE"
    author: Ably
    url: https://ably.com/blog/websockets-vs-sse
    type: technical_blog
    published: 2025-03-12
    reliability: high
  - id: src5
    title: "Polling vs. Long Polling vs. SSE vs. WebSockets vs. Webhooks"
    author: AlgoMaster
    url: https://blog.algomaster.io/p/polling-vs-long-polling-vs-sse-vs-websockets-webhooks
    type: technical_blog
    published: 2025-01-18
    reliability: moderate_high
  - id: src6
    title: "The WebSocket Protocol (RFC 6455)"
    author: IETF
    url: https://datatracker.ietf.org/doc/html/rfc6455
    type: rfc_spec
    published: 2011-12-01
    reliability: authoritative
  - id: src7
    title: "WebTransport API"
    author: MDN Web Docs
    url: https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API
    type: official_docs
    published: 2025-11-01
    reliability: authoritative
---

# Long Polling vs SSE vs WebSocket: Complete Reference

## TL;DR

- **Bottom line**: Use SSE for server-to-client streaming (dashboards, notifications, feeds); use WebSocket for bidirectional real-time communication (chat, gaming, collaborative editing); use long polling only when SSE/WebSocket are unavailable (legacy browsers, restrictive proxies).
- **Key tool/command**: `new EventSource('/events')` for SSE; `new WebSocket('wss://host/ws')` for WebSocket
- **Watch out for**: Using WebSocket when SSE would suffice -- SSE auto-reconnects, works through all HTTP proxies, and needs no library. In 80% of "need WebSocket" cases, SSE is enough.
- **Works with**: All modern browsers (SSE: 97%+ support; WebSocket: 98%+ support; WebTransport: experimental, Chromium only as of Feb 2026)

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- SSE only supports UTF-8 text data server-to-client; it cannot send binary data or receive messages from the client on the same connection
- WebSocket requires explicit upgrade handshake (`HTTP 101 Switching Protocols`); corporate proxies that don't understand this will drop the connection -- always use WSS (TLS) in production [src4]
- HTTP/1.1 limits SSE to 6 concurrent connections per domain across all browser tabs; this is removed with HTTP/2 multiplexing [src1]
- WebTransport (HTTP/3) is not production-ready as of February 2026 (W3C Working Draft); do not deploy without a WebSocket fallback [src7]
- Long polling holds server threads/connections open; without proper timeout and connection pooling, it will exhaust server resources under load [src5]

## Quick Reference

| Feature | Short Polling | Long Polling | SSE | WebSocket | WebTransport |
|---|---|---|---|---|---|
| **Direction** | Client-to-server | Client-to-server | Server-to-client | Bidirectional | Bidirectional |
| **Latency** | High (poll interval) | Medium (held request) | Low (~same as WS) | Low (~3ms advantage) | Lowest (UDP optional) |
| **Per-message overhead** | Full HTTP headers (~800B) | Full HTTP headers (~800B) | ~5 bytes/message | ~2 bytes/frame | ~2 bytes/frame |
| **Auto-reconnect** | N/A (new request each time) | Manual | Built-in (EventSource) | Manual | Manual |
| **Binary support** | Yes (via encoding) | Yes (via encoding) | No (UTF-8 text only) | Yes (native) | Yes (native + datagrams) |
| **HTTP/2 multiplexing** | Yes | Yes | Yes (removes 6-conn limit) | No (separate TCP) | Yes (QUIC streams) |
| **Browser support** | Universal | Universal | 97%+ (no IE) | 98%+ | Chromium only (experimental) |
| **Proxy friendliness** | Excellent | Good | Excellent (standard HTTP) | Poor (needs upgrade) | Poor (needs HTTP/3) |
| **Scaling difficulty** | Low (stateless) | High (held connections) | Medium (long-lived HTTP) | High (stateful connections) | Medium (QUIC) |
| **Connection setup** | New HTTP request each time | New HTTP request each time | Single HTTP stream | HTTP upgrade to TCP | HTTP/3 CONNECT |
| **Max connections/domain** | Browser-limited | Browser-limited | 6 (HTTP/1.1), unlimited (HTTP/2) | Browser-limited | Browser-limited |
| **Ideal use case** | Dashboards with 30s+ intervals | Legacy compatibility | Live feeds, notifications | Chat, gaming, collab editing | Future: 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]

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

app.get('/events', (req, res) => {
  // Required SSE headers
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'X-Accel-Buffering': 'no' // Disable Nginx buffering
  });

  // Send initial comment to establish connection
  res.write(':ok\n\n');

  // Send events on an interval (replace with real event source)
  const interval = setInterval(() => {
    const data = JSON.stringify({ time: new Date().toISOString() });
    res.write(`id:${Date.now()}\nevent:update\ndata:${data}\n\n`);
  }, 1000);

  // Clean up on client disconnect
  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
});

app.listen(3000);
```

**Verify**: `curl -N http://localhost:3000/events` --> expected: 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]

```javascript
// Browser client for SSE
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. Use the `ws` library for Node.js -- it is the most widely used and does not bundle unnecessary features. [src6]

```javascript
// Node.js WebSocket server using 'ws' library
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', (ws, req) => {
  console.log('Client connected from', req.socket.remoteAddress);

  // Handle incoming messages
  ws.on('message', (message) => {
    const parsed = JSON.parse(message);
    // Echo back or broadcast
    wss.clients.forEach((client) => {
      if (client.readyState === 1) { // WebSocket.OPEN
        client.send(JSON.stringify({ from: 'server', ...parsed }));
      }
    });
  });

  // Handle disconnection
  ws.on('close', (code, reason) => {
    console.log(`Disconnected: ${code} ${reason}`);
  });

  // Send welcome message
  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. You must implement reconnection with exponential backoff. [src4]

```javascript
// Browser WebSocket client with exponential backoff
function createWebSocket(url) {
  let ws;
  let retries = 0;
  const maxRetries = 10;
  const baseDelay = 1000; // 1 second

  function connect() {
    ws = new WebSocket(url);

    ws.onopen = () => {
      console.log('Connected');
      retries = 0; // Reset on successful connection
    };

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      console.log('Received:', data);
    };

    ws.onclose = (event) => {
      if (!event.wasClean && retries < maxRetries) {
        const delay = Math.min(baseDelay * Math.pow(2, retries), 30000);
        console.log(`Reconnecting in ${delay}ms (attempt ${retries + 1})`);
        setTimeout(connect, delay);
        retries++;
      }
    };

    ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
  }

  connect();
  return { getSocket: () => ws };
}

const client = createWebSocket('wss://example.com/ws');
```

**Verify**: Kill and restart the server --> client should reconnect automatically with increasing delays

## Code Examples

### Python: SSE Server with Flask

```python
# 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():
    """Generator that yields SSE-formatted events."""
    while True:
        data = json.dumps({"time": time.time()})
        # SSE format: "data: ...\n\n"
        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

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

import asyncio
import websockets
import 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})
            # Broadcast to all connected clients
            await asyncio.gather(
                *(client.send(broadcast) for client in connected
                  if client.open)
            )
    finally:
        connected.discard(websocket)

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

asyncio.run(main())
```

### Go: SSE Server

> Full script: [go-sse-server.go](scripts/go-sse-server.go) (33 lines)

```go
// Input:  HTTP GET /events
// Output: text/event-stream with periodic updates
package main
import (
    "fmt"
# ... (see full script)
```

## Anti-Patterns

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

```javascript
// 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

```javascript
// 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.
// Server is standard HTTP -- scales with existing infrastructure.
```

### Wrong: Short polling for real-time chat

```javascript
// 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

```javascript
// GOOD -- single persistent connection, messages delivered instantly
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

```javascript
// 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

```javascript
// 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

- **SSE 6-connection limit on HTTP/1.1**: Browsers allow only 6 simultaneous HTTP/1.1 connections per domain. If a user opens 6+ tabs, SSE connections in new tabs will queue indefinitely. Fix: Serve over HTTP/2 (which multiplexes all streams over one TCP connection) or use a different subdomain. [src1]
- **Nginx buffering breaks SSE**: Nginx buffers responses by default, which prevents SSE events from reaching the client in real time. Fix: Add `X-Accel-Buffering: no` response header or set `proxy_buffering off` in Nginx config. [src2]
- **WebSocket through reverse proxies**: Load balancers and reverse proxies (HAProxy, Nginx, AWS ALB) require explicit WebSocket upgrade configuration. Without it, the HTTP 101 upgrade is silently dropped. Fix: Configure `proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";` in Nginx. [src4]
- **Missing heartbeat/keepalive**: Both SSE and WebSocket connections can be silently terminated by intermediate proxies (typically after 60-120 seconds of inactivity). Fix: Send a comment line (SSE: `:keepalive\n\n`) or ping frame (WebSocket) every 30 seconds. [src3]
- **Not handling `Last-Event-ID` for SSE**: When an SSE client reconnects, it sends the last received event ID in the `Last-Event-ID` header. If the server ignores this, the client misses events during the disconnection window. Fix: Track event IDs server-side and replay missed events on reconnect. [src1]
- **Memory leaks from unclosed connections**: Forgetting to remove event listeners or clean up intervals when a client disconnects leaks memory on the server. Fix: Always listen for `close`/`end` events and clean up resources. [src5]
- **WebSocket message ordering assumptions**: WebSocket guarantees in-order delivery per connection, but if you broadcast from multiple server instances, messages may arrive out of order. Fix: Include sequence numbers or timestamps in messages and reorder client-side. [src6]

## Diagnostic Commands

```bash
# 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 When | Don't Use When | Use Instead |
|---|---|---|
| Server pushes updates to browser (dashboards, feeds, alerts) | Client needs to send frequent messages to server | WebSocket |
| You want zero-library client code with auto-reconnect | You need binary data transfer (images, audio, files) | WebSocket |
| Running behind corporate proxies or strict firewalls | Building real-time multiplayer gaming | WebSocket or WebTransport |
| HTTP/2 is available and you have many concurrent streams | IE11 support is required and polyfills are not acceptable | Long polling |
| Building chat, collaborative editing, or live gaming | Data updates every 30+ seconds with no urgency | Short polling (simpler) |
| Both client and server send messages frequently | Proxy/firewall blocks WebSocket and WSS fallback fails | SSE + HTTP POST |
| Legacy browser/proxy environment blocks SSE and WebSocket | Latency and efficiency matter | SSE or WebSocket (with polyfill if needed) |

## Important Caveats

- Performance benchmarks show SSE and WebSocket have nearly identical throughput; WebSocket has a ~3ms latency advantage that is negligible in production where network RTT dominates [src3]
- Safari on iOS terminates SSE connections when the app goes to background; use the Page Visibility API to detect this and reconnect on `visibilitychange` [src2]
- WebTransport is included in Interop 2026 for cross-browser standardization, but as of February 2026 it is Chromium-only and experimental -- do not depend on it without a fallback [src7]
- Socket.IO, SignalR, and similar libraries abstract over these protocols with automatic fallback; they add 15-50KB to bundle size but can simplify implementation when you need cross-protocol compatibility [src4]
- AWS API Gateway WebSocket API has a 32KB message size limit and 10-minute idle timeout; for larger payloads or longer sessions, use a dedicated WebSocket server [src5]

## Related Units
<!-- Generated from related_kos frontmatter -->

- [Webhook Delivery System Design](/software/system-design/webhooks-system/2026)
- [Notification System Architecture](/software/system-design/notification-system/2026)
- [Message Queue & Event-Driven Architecture](/software/system-design/message-queue-event-driven/2026)
