ID

Home·Writings

WebSockets vs SSE: Which to Use for Real-Time Features

When to use WebSockets vs SSE

By ·

A practical comparison: duplex vs one-way push. Use a rare site toast as the example of when SSE (or a short poll) beats an idle WebSocket per client.

Teams reach for WebSockets whenever someone says “real-time.” That is often right for chat and presence. It is often wrong for a rare, one-way interrupt.

This is a comparison of the two, with one concrete product shape as the example: a site toast that says something important just went live ({ id, title, href }), then goes away.

Side by side

DimensionSSEWebSockets
DirectionServer → clientFull duplex
ProtocolHTTP stream (EventSource)Persistent socket
Best whenPush notifications, status, rare alertsChat, collab, presence, trading ticks
Idle costOne long HTTP request (or poll instead)Connection + often sticky LB state
Client APIEventSource, reconnect built inCustom open/heartbeat/reconnect
PayloadEvent stream, usually smallAnything, often high chatter

Rule of thumb: match transport to frequency and direction.

SignalPrefer
Rare + one-way + smallSSE (or short poll for an MVP)
Continuous + two-way + busy pipeWebSockets

The toast example

Imagine a homepage toast: “Special report is live” → link into the piece → dismissible per reader.

What it is not: chat, presence, collaborative cursors.

What it is:

  • Rare (a few times a week, not a firehose)
  • One-way (server → client; dismiss is local only)
  • Small ({ id, title, href } or null)
  • Lag-tolerant (a few seconds is fine)

Keeping a WebSocket open for every idle tab “just in case” pays for an event that mostly never fires. That is the wrong interrupt.

What fits the toast

ApproachFit
Short poll GET /alerts/current every 15–30s + on tab focusFine MVP. Cache-friendly.
SSE on /alerts/streamPush without duplex.
WebSocketOverkill here. Save it for two-way surfaces.

Dismiss stays client-only (localStorage by alert id). Closing the toast for you must not clear it for everyone else.

Why SSE wins for that shape

  1. Direction matches. The toast is server → client. SSE is that pipe. WebSockets give you duplex you will mostly ignore.
  2. Ops are simpler. SSE rides normal HTTP. Fewer sticky-session stories than a WS fleet on day one.
  3. Idle cost. Quiet tabs are the common case for alerts. Polling with a short TTL cache is cheap. SSE is still lighter than framing a “real-time platform” around rare events. WebSockets shine when the pipe is busy.
  4. You can graduate. Ship poll → swap the client to EventSource later. The domain model (one active alert, public DTO, cache key) does not change.

Minimal contract (toast / alert)

Public:

  • GET /alerts/current → alert or null (first paint + non-streaming clients)
  • GET /alerts/stream → SSE; emit on activate/clear (plus an occasional heartbeat comment so proxies do not kill idle connections)

Admin still publishes through the normal authenticated path. The stream is a read-model fan-out, not a second write path.

Multi-instance API: fan-out the domain event over Redis pub/sub (or your bus) so each process can push to its SSE clients. Single box: an in-process emitter is enough.

When WebSockets are right

Use them when the session is conversational or continuous:

  • Live chat / studio comments
  • Collaborative editing
  • Multiplayer presence
  • High-frequency ticks where clients also send

If the product sentence is “users talk to each other in real time,” sockets earn their keep.

If the sentence is “tell everyone something important just went live,” that is a notification edge: SSE or poll, not an always-on duplex farm.

Takeaway

SignalPrefer
Rare, one-way, small (e.g. site toast)SSE or short poll
Busy, bidirectional, continuousWebSockets

The wrong abstraction is not “real-time.” It is always-on duplex for a rare one-way alert.