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:
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:
30 seconds
polling every 20-30 seconds may be the right solution.
If it is:
< 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
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
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
server -> client
SSE gives event framing and reconnection out of the box.
Both sides send messages constantly
client <-> server
WebSocket is the natural choice.
We need different classes of transport
reliable streams
+
independent streams
+
unreliable datagrams
Then WebTransport is worth considering.
Decision tree
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
| Mechanism | Model | Direction | Connection state | Delivery model |
|---|---|---|---|---|
| Polling | repeated request/response | client-driven | short | HTTP |
| Long Polling | delayed request/response | server-triggered response | long request | HTTP |
| HTTP Streaming | streaming response | server to client | long response | bytes/stream |
| SSE | event stream over HTTP | server to client | persistent | reliable ordered stream |
| WebSocket | message channel | bidirectional | persistent | reliable ordered |
| WebTransport | streams + datagrams | bidirectional | persistent session | reliable + 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:
events
acknowledgements
rooms
namespaces
reconnect
broadcasting
transport fallback
The architecture is closer to:
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:
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:
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:
request
response
forget
we move to:
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.