Websockets Vs Sse: Real-Time Communication Explained

WebSockets vs Server-Sent Events vs Long Polling: Real-Time Communication Explained
8 views

Real-time features are now expected in every web application — live notifications, chat, dashboards, collaborative editing. Three techniques deliver this: WebSockets (bidirectional), Server-Sent Events (server-to-client only), and Long Polling (HTTP-based fallback). Choosing the right one determines your scalability, complexity, and infrastructure costs. This guide focuses on WebSockets vs SSE, with the production trade-offs senior engineers need to make sound decisions.

⚡ TL;DR: WebSockets for bidirectional real-time (chat, gaming, collaborative editing). SSE for server-to-client streams (notifications, live feeds, AI streaming responses). Long Polling for simple real-time on infrastructure that blocks WebSockets. SSE is underrated — it is simpler than WebSockets for one-directional data.

Long Polling — the HTTP fallback — WebSockets vs SSE

// Client: make request, server holds it until data is available or timeout
// Then client immediately makes another request

// Server (Express):
app.get('/poll', async (req, res) => {
  const timeout = 30000; // 30s max hold
  const deadline = Date.now() + timeout;

  while (Date.now() < deadline) {
    const event = await checkForNewEvents(req.user.id);
    if (event) return res.json(event);
    await sleep(500); // Check every 500ms
  }

  res.json({ type: 'timeout' }); // No event in 30s
});

// Client:
async function poll() {
  while (true) {
    try {
      const event = await fetch('/poll').then(r => r.json());
      if (event.type !== 'timeout') handleEvent(event);
    } catch (e) { await sleep(2000); } // Retry on error
  }
}
// Pros: works everywhere, no special infrastructure
// Cons: slow (500ms-1s latency), inefficient (constant connections)

Server-Sent Events — the underrated choice

// SSE: server pushes events over persistent HTTP connection
// One-directional: server → client only
// Built-in reconnection, event IDs, named events

// Server:
app.get('/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // Send events
  const sendEvent = (data, event = 'message', id = Date.now()) => {
    res.write(`id: ${id}\n`);
    res.write(`event: ${event}\n`);
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };

  sendEvent({ msg: 'Connected!' }, 'connected');

  // Subscribe to events for this user
  const unsubscribe = eventBus.subscribe(req.user.id, (event) => {
    sendEvent(event, event.type);
  });

  // Cleanup on disconnect
  req.on('close', () => unsubscribe());
});

// Client: built-in browser API with automatic reconnection
const es = new EventSource('/events');
es.addEventListener('notification', (e) => {
  const data = JSON.parse(e.data);
  showNotification(data);
});
es.addEventListener('error', () => {
  // Auto-reconnects after 3 seconds by default
});

// SSE advantages over WebSockets for one-directional:
// - Uses regular HTTP (works through proxies, load balancers)
// - Built-in reconnection with last event ID
// - Much simpler to implement and scale

WebSockets — bidirectional real-time

// WebSockets: persistent TCP-like connection over HTTP upgrade
// True bidirectional: client ↔ server

// Server (Node.js with ws library):
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });

const rooms = new Map();

wss.on('connection', (ws, req) => {
  const userId = authenticateUser(req);
  ws.userId = userId;

  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);
    switch (msg.type) {
      case 'join_room':
        if (!rooms.has(msg.roomId)) rooms.set(msg.roomId, new Set());
        rooms.get(msg.roomId).add(ws);
        break;
      case 'chat':
        // Broadcast to room
        rooms.get(msg.roomId)?.forEach(client => {
          if (client !== ws && client.readyState === 1) {
            client.send(JSON.stringify({ type: 'chat', msg: msg.text, from: userId }));
          }
        });
    }
  });

  ws.on('close', () => {
    rooms.forEach(room => room.delete(ws));
  });
});

// Client:
const ws = new WebSocket('wss://api.example.com/ws');
ws.onopen = () => ws.send(JSON.stringify({ type: 'join_room', roomId: 'general' }));
ws.onmessage = (e) => handleMessage(JSON.parse(e.data));
ws.onclose = () => setTimeout(reconnect, 1000); // Manual reconnect

Decision guide

  • ✅ SSE: notifications, live feeds, AI token streaming, dashboards — simple and scalable
  • ✅ WebSockets: chat, collaborative editing, multiplayer gaming, live code collaboration
  • ✅ Long Polling: environments that block WebSockets, simple status updates
  • ❌ Don’t use WebSockets when SSE works — WebSockets need sticky sessions or Redis pub/sub for horizontal scaling
  • ❌ Don’t use Long Polling if SSE or WebSockets are available — 10x more server connections

WebSocket scaling is a system design concern — the rate limiter guide covers how to throttle WebSocket message rates. For Lambda streaming (SSE equivalent in serverless), see the Lambda Function URL streaming guide. External reference: MDN Server-Sent Events documentation.

Recommended Reading

→ Designing Data-Intensive Applications — The essential book every senior developer needs. Covers distributed systems, databases, and production architecture.

→ The Pragmatic Programmer — Timeless engineering wisdom for writing better, more maintainable code at any level.

Affiliate links. We earn a small commission at no extra cost to you.

Free Weekly Newsletter

🚀 Don’t Miss the Next Cheat Code

Join 1,000+ senior developers getting expert-level JS, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.

✓ No spam✓ Unsubscribe anytime✓ Expert-level only

Related: Consistent Hashing for WebSockets: Sticky Gateways During Scale Events · Chunk Overlap Evaluation: Precision Versus Continuity for Long Functions · Event-Driven Architecture: Building Decoupled Systems With Events and Message Queues

Related: DynamoDB Streams Outbox: Domain Events Without Dual-Write Failures · Lambda Test Events as Contracts: Schema-Locked Fixtures Across Teams · Feature Stores vs Inline Compute: Real-Time Decisions Without Training Skew

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a comment

No account needed. Name and email are optional.