Konrad Kowalski (rootsher)Principal Platform & Reliability Architect000000010001011101011101100101010101100111111111

Polling, Streaming, SSE, WebSocket, or WebTransport?

date
category
Backend
also in
Frontend · System Design
reading
3 min / 546 words

There is no such thing as a "realtime technology".

There are different requirements around:

text
latency
direction
frequency
ordering
reliability
connection lifetime

Only their combination leads to a mechanism.

Start with the latency budget

The first question:

how stale can the data be?

If the answer is:

text
30 seconds

polling every 20-30 seconds may be the right solution.

If it is:

text
< 500 ms

constant re-asking becomes less attractive.

So realtime is above all a requirement about freshness.

Who initiates the data?

The client periodically needs a current snapshot

text
client -> server

Pick polling.

Typical cases:

  • the status of a batch job,
  • a dashboard without a strict SLA,
  • availability checks.

The server knows when a change appears

Long polling can make sense, especially when we want to stay with classic request/response.

One request produces data gradually

text
request
<- chunk
<- chunk
<- chunk
<- done

That is raw HTTP streaming.

A typical example:

  • an AI response,
  • logs,
  • an export or a generated result.

The server publishes events over a long time

text
server -> client

SSE gives event framing and reconnection out of the box.

Both sides send messages constantly

text
client <-> server

WebSocket is the natural choice.

We need different classes of transport

text
reliable streams
+
independent streams
+
unreliable datagrams

Then WebTransport is worth considering.

Decision tree

text
How fresh must data be?
│
├── seconds/minutes are OK
│   └── Polling
│
└── low latency
    │
    ├── one finite response produced gradually
    │   └── HTTP Streaming
    │
    ├── long-lived server to client events
    │   └── SSE
    │
    └── frequent bidirectional communication
        │
        ├── reliable ordered messages are enough
        │   └── WebSocket
        │
        └── need multiple streams / datagrams
            └── WebTransport

Long polling remains a useful option between polling and a persistent stream, especially with compatibility constraints or existing infrastructure.

A comparison

MechanismModelDirectionConnection stateDelivery model
Pollingrepeated request/responseclient-drivenshortHTTP
Long Pollingdelayed request/responseserver-triggered responselong requestHTTP
HTTP Streamingstreaming responseserver to clientlong responsebytes/stream
SSEevent stream over HTTPserver to clientpersistentreliable ordered stream
WebSocketmessage channelbidirectionalpersistentreliable ordered
WebTransportstreams + datagramsbidirectionalpersistent sessionreliable + unreliable

Socket.IO is not another transport

If we pick WebSocket, one question remains:

how much of the application protocol do we want to write ourselves?

A native WebSocket gives a channel.

Socket.IO gives a higher model:

text
events
acknowledgements
rooms
namespaces
reconnect
broadcasting
transport fallback

The architecture is closer to:

text
application
   |
   v
Socket.IO
   |
   v
Engine.IO
   |
   v
WebSocket / HTTP long-polling

So Socket.IO and WebSocket are not alternatives at the same level.

Socket.IO is a choice of the communication layer above the transport.

GraphQL Subscriptions also sit higher

GraphQL defines a subscription as a long-running operation producing a response stream in reaction to successive events.

It does not define a specific transport.

So this is possible:

text
GraphQL Subscription
        |
        v
WebSocket

and so are other transport mappings.

That is application-level semantics, not an equivalent of WebSocket.

What about WebRTC?

A WebRTC DataChannel also provides realtime data communication, but it changes the topology:

text
frontend A <-> frontend B

The backend can take part in signaling, STUN/TURN and setting up the session, but the resulting data flow can be peer to peer.

That is why WebRTC belongs to a separate story.

The most important trade-off

Every step towards persistent realtime increases the responsibility of the system.

From:

text
request
response
forget

we move to:

text
connection identity
liveness
heartbeat
reconnect
resume
fan-out
backpressure
deploy draining
state recovery

That is a cost which should be justified by the requirements of the product.

The simplest correct mechanism usually wins.

Not because realtime is hard.

Because persistent communication turns a system from a series of independent operations into a long-running relationship between the client, the network and the backend.

And that is the right architectural boundary of this series.