Real-time features — live chat, notifications, dashboards, collaborative editing — each need a different data delivery pattern. WebSocket, Server-Sent Events (SSE), and polling solve different problems. Here's when to use each, with code examples.

Quick Comparison

WebSocketSSE (Server-Sent Events)Short PollingLong Polling
DirectionBidirectionalServer → Client onlyClient → Server (request/response)Client → Server (request/response)
LatencyNear real-timeNear real-timeDepends on intervalLow (held connection)
Browser supportAll modernAll (except IE)UniversalUniversal
HTTP/2 friendlyN/A (upgrades from HTTP)YesYesYes
ReconnectionManual (build it)Built-in (automatic)ManualManual
ComplexityHighLowLowestModerate

WebSocket — Bidirectional Real-Time

WebSocket is the only option when both client and server need to push messages. Use it for: chat applications, collaborative editing, multiplayer games, live auctions, trading platforms.

// Server (using ws)
import { WebSocketServer } from "ws";

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

wss.on("connection", (ws, req) => {
  const userId = authenticate(req);

  ws.on("message", (data) => {
    const message = JSON.parse(data.toString());
    // Broadcast to all clients in a room
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({ user: userId, text: message.text }));
      }
    });
  });

  ws.on("close", () => {
    // Handle disconnect
  });
});

Server-Sent Events (SSE) — Simple Server Push

SSE is simpler than WebSocket when you only need server → client updates. Built-in reconnection, works over HTTP/2, and doesn't need a special protocol. Use for: live dashboards, notification feeds, progress bars, log streaming, sports scores.

// Server (Hono)
app.get("/events", (c) => {
  return c.streamText(async (stream) => {
    // Send events as they happen
    stream.write(`data: ${JSON.stringify({ type: "connected" })}

`);

    const interval = setInterval(async () => {
      const updates = await getUpdates();
      stream.write(`data: ${JSON.stringify(updates)}

`);
    }, 1000);

    // Auto-cleanup on client disconnect
    stream.onAbort(() => clearInterval(interval));
  });
});

// Client (native EventSource — zero dependencies)
const es = new EventSource("/events");
es.onmessage = (event) => {
  const data = JSON.parse(event.data);
  updateUI(data);
};

Short Polling — Simplest, Least Efficient

Client sends a request every N seconds. Simple but wasteful — most requests return empty. Only use when you can't use SSE or WebSocket (e.g., legacy infrastructure) or when data changes very infrequently.

// Client — poll every 30 seconds
setInterval(async () => {
  const res = await fetch("/api/updates?since=" + lastUpdate);
  if (res.ok) {
    const updates = await res.json();
    if (updates.length) updateUI(updates);
  }
}, 30000);

Long Polling — Polling Without the Waste

Client sends a request, server holds it open until there's new data (or a timeout). The client immediately reconnects after receiving a response. This is how most "real-time" APIs worked before WebSocket existed.

Best for: Legacy systems that can't upgrade to WebSocket, firewalls that block WebSocket connections, APIs where the client can't maintain persistent connections.

Decision Matrix

FeatureBest PatternWhy
Chat / messagingWebSocketBidirectional, low latency
Live dashboard / feedSSESimpler than WebSocket. Built-in reconnect. HTTP/2 friendly.
NotificationsSSE or Web PushSSE if browser is open. Web Push for background notifications.
Progress bar / file uploadSSEPush progress from server. Simple to implement.
Collaborative editingWebSocket (or CRDT)Low latency bidirectional required.
Simple status checkShort PollingIf data changes every 5+ minutes, polling is fine.

Bottom line: Use SSE for server→client streaming — it's simpler than WebSocket, HTTP/2 friendly, and has built-in reconnection. Use WebSocket only when you need bidirectional communication. Use polling only as a last resort. Server-Sent Events is the most underrated real-time pattern. See also: Backend Framework Comparison and Edge Functions Comparison.