WebSocket vs HTTP Polling vs SSE — When to Use Each
Fast recall
Quick revision
Core answer
Whether the candidate understands protocol trade-offs and can select the right tool for the job.
Reaching for WebSockets by default even when SSE or long-polling would be simpler and sufficient.
Mention connection overhead, HTTP/2 multiplexing affecting SSE's scaling story, and browser support caveats.
Interview-ready guide
Interview answer
WebSockets, SSE, and HTTP polling are three different solutions to the real-time communication problem, each with different infrastructure requirements, browser compatibility, and data direction capabilities.
Server-Sent Events — the right default for server-to-client push:
- SSE works as a persistent HTTP response — existing load balancers, access logs, TLS termination, and monitoring all work without changes because it's plain HTTP
- Built-in reconnection with
retry:fields andLast-Event-IDreplay for free — things you would have to build from scratch with WebSocket - Under HTTP/2, multiple SSE streams multiplex over a single connection, removing the old six-connection-per-origin browser limit
WebSockets — necessary for continuous bidirectional streaming:
- Becomes necessary when the client needs to continuously stream data upstream: chat messages, cursor movements in collaborative editors, game inputs at high frequency
- After the HTTP upgrade handshake, both sides exchange compact binary frames with 2–14 bytes of overhead vs hundreds of bytes in HTTP headers per polling request
- The operational trade-off: every proxy in the path must support the Upgrade header, and you own reconnection, replay, and session management entirely
HTTP polling — the honest fallback:
- Appropriate when real-time isn't critical, or when network infrastructure blocks persistent connections; finance desktops behind legacy proxies are a real use case
- The scaling problem: 10,000 clients polling every 5 seconds produce 2,000 requests per second even when nothing changed; SSE and WebSocket push only when data actually exists
The practical recommendation:
- The REST+SSE hybrid — SSE for pushing updates, REST POST for mutations — avoids WebSocket's infrastructure complexity while providing real-time push for most "real-time" feature requirements
I recommend reaching for WebSocket only when continuous upstream data rate genuinely justifies the infrastructure overhead, and SSE for the vast majority of notification and update scenarios.
WebSocket vs HTTP Polling vs SSE — When to Use Each
Direct Answer
Use WebSockets for full-duplex, low-latency communication (chat, games, live collaboration). Use SSE for server-to-client streams (notifications, feeds). Use HTTP polling only when real-time is not critical or WebSockets are blocked.
Code Example
All three deliver near-real-time data. Polling re-requests on a timer, SSE is a one-way server → client stream over HTTP, WebSocket is a full-duplex persistent connection.
// ---------- 1) HTTP long-polling ----------
@RestController
public class PollingController {
@GetMapping("/notifications/poll")
public DeferredResult<List<Notification>> poll(@RequestParam long since) {
DeferredResult<List<Notification>> result = new DeferredResult<>(30_000L);
notificationBus.subscribe(since, result::setResult); // fires when new data arrives
result.onTimeout(() -> result.setResult(List.of())); // client retries on empty
return result;
}
}
// Client: every time it receives data or times out, it re-issues GET /notifications/poll?since=...
// ---------- 2) Server-Sent Events (SSE) ----------
@RestController
public class SseController {
@GetMapping(path = "/notifications/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter stream(@AuthenticationPrincipal Jwt jwt) {
SseEmitter emitter = new SseEmitter(Duration.ofHours(1).toMillis());
notificationBus.subscribe(jwt.getSubject(), event -> {
try { emitter.send(SseEmitter.event().name("notification").data(event)); }
catch (IOException e) { emitter.completeWithError(e); }
});
return emitter;
}
}
// Client: const es = new EventSource("/notifications/stream");
// es.addEventListener("notification", e => console.log(JSON.parse(e.data)));
// ---------- 3) WebSocket (full duplex) ----------
@Configuration
@EnableWebSocketMessageBroker
public class WsConfig implements WebSocketMessageBrokerConfigurer {
@Override public void registerStompEndpoints(StompEndpointRegistry r) {
r.addEndpoint("/ws").withSockJS();
}
@Override public void configureMessageBroker(MessageBrokerRegistry r) {
r.enableSimpleBroker("/topic", "/queue");
r.setApplicationDestinationPrefixes("/app");
}
}
@Controller
public class ChatController {
@MessageMapping("/chat") // client → server
@SendTo("/topic/chat") // server → all subscribers
public ChatMessage send(ChatMessage m) { return m; }
}
// Client: const sock = new SockJS("/ws");
// const stomp = Stomp.over(sock);
// stomp.connect({}, () => {
// stomp.subscribe("/topic/chat", msg => console.log(JSON.parse(msg.body)));
// stomp.send("/app/chat", {}, JSON.stringify({text: "hi"}));
// });
| Feature | Polling | SSE | WebSocket |
|---|---|---|---|
| Direction | client ↔ server | server → client only | full duplex |
| Transport | plain HTTP | HTTP (text/event-stream) | WS frames over TCP |
| Connection | one per request | one long-lived | one long-lived |
| Proxy / firewall friendliness | best | very good | sometimes blocked (needs 101 Upgrade) |
| Reconnect | implicit (each request) | automatic, with Last-Event-ID | manual |
| Back-pressure / flow control | limited | basic | yes (WS buffering) |
| Best fit | simple status updates, compatibility | dashboards, notifications, logs | chat, games, collaborative editing |
- Polling is the last resort — use long-polling (
DeferredResult) if anything, and only when the infra blocks persistent connections. - SSE is under-used and usually the right answer for "server pushes updates to the browser": automatic reconnect with
Last-Event-ID, plain HTTP, works through every proxy, one-direction is almost always enough. - WebSocket pays off when the client also needs to stream to the server (chat typing indicators, collaborative cursors, multiplayer game inputs). STOMP over SockJS in Spring gives message brokering + fallback.
- Behind load balancers, sticky sessions (or a shared broker like Redis/RabbitMQ) are required for WS/SSE so reconnects find state.
Common Mistakes & Pitfalls
Reaching for WebSockets by default even when SSE or long-polling would be simpler and sufficient.
Check your understanding
Answer these without looking back at the article.
- How does HTTP/2 change the SSE scaling story?
- How would you handle WebSocket reconnection logic on the client side?
- What are the load balancer implications of each approach?
Related interview prep categories
1Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.