Skip to the chapter
←JavaScriptGGroundworkJSJavaScript
JavaScript/Intermediate/I8
25 / 41 written chapters
/

The cover
  1. B1Setup & mental model
  2. B2Execution context
  3. B3var, let & const
  4. B4Scope
  5. B5Single thread
  6. B6In the browser
  7. B7Types & values
  8. B8Strings & Math
  9. B9Operators & flow
  10. B10Functions
  11. B11Objects & arrays
  12. B12this
  13. B13Closures
  14. B14DOM & events
  15. B15Callbacks & promises
  16. B16Errors & tools
  17. B17Guided project
  18. ★Cheat page
  1. I1Scope & functions, properly
  2. I2Objects deeply
  3. I3Prototypes & OOP
  4. I4Async, properly
  5. I5Modules & tooling
  6. I6Regex & dates
  7. I7Error handling
  8. I8Real-time connections
  9. I9Offline & storage
  10. I10Browser APIs
  11. I11Modern JS
  12. ★Cheat page
  1. A1Engine & memory
  2. A2Advanced async
  3. A3Metaprogramming
  4. A4Types & data
  5. A5Patterns & architecture
  6. A6Performance
  7. A7Security
  8. A8Ecosystem
  9. A9Testing
  10. A10Observability
  11. A11Interview bank
  12. A12Build it yourself
  13. ★Cheat page

JSI8 · Part 2 · Intermediate

Real-time connections

fetch answers one question at a time. This is what answers a stream of them.

5 min read5 sections2 exercises
25 of 41

In this chapter

  • WebSocket
  • Server-Sent Events (EventSource)
  • Long polling
  • Deciding between the three
  • Reconnecting without hammering the server
about 5 min
05On this page
  1. WebSocket
  2. Server-Sent Events (EventSource)
  3. Long polling
  4. Deciding between the three
  5. Reconnecting without hammering the server

Nothing below is a runnable example — every option here needs a real server on the other end, so a live demo in this sandbox would either hang or fail for reasons that have nothing to do with the code being right or wrong. The one genuinely testable piece — reconnect backoff — gets its own runnable example further down.

WebSocket

A single, long-lived, two-way connection — either side can send a message at any time, with no request/response pairing required. It starts as a normal HTTP request that asks to upgrade the connection; once the server agrees, the same TCP connection stops speaking HTTP and starts speaking the WebSocket frame format instead.

const socket = new WebSocket("wss://example.com/chat");

socket.onopen = () => socket.send(JSON.stringify({ type: "join", room: "general" }));
socket.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  console.log("received:", msg);
};
socket.onerror = (event) => console.error("socket error", event);
socket.onclose = (event) => console.log("closed:", event.code, event.reason);

socket.send(JSON.stringify({ type: "message", text: "hi" }));
socket.readyStateMeans
0 — CONNECTINGhandshake in progress
1 — OPENready — the only state .send() actually works in
2 — CLOSINGclosing handshake started
3 — CLOSEDfully closed, or never connected at all
⚠ A WebSocket does not reconnect itself A dropped connection — the server restarts, a phone switches from wifi to mobile data — just fires onclose and stops there. Every real WebSocket client reconnects manually: catch onclose, wait (ideally with backoff — see below), and open a fresh new WebSocket(url).

Dry run: readyState through the socket's lifecycle

StepEvent / callreadyState beforereadyState afterWhat happens
1new WebSocket("wss://example.com/chat")—0 (CONNECTING)handshake starts; nothing has been sent yet
2handshake completes01 (OPEN)onopen fires and sends the "join" message
3socket.send(JSON.stringify({ type: "message", text: "hi" }))11succeeds — .send() only works while OPEN
4server pushes a message11onmessage fires with the parsed payload
5either side starts closing12 (CLOSING)closing handshake in progress; a .send() now throws
6closing handshake finishes23 (CLOSED)onclose fires with a code and reason — nothing reconnects on its own

readyState only ever moves forward through these four numbers, never backward — once a socket reaches CLOSED there is no "reopen," only constructing a brand-new WebSocket.

Server-Sent Events (EventSource)

The one-directional version — server to client only, over a plain HTTP response the server keeps open and keeps writing to. The browser's built-in EventSource handles the entire protocol, including something WebSocket makes you build yourself: automatic reconnection.

const events = new EventSource("/api/notifications");

events.onmessage = (event) => console.log("update:", event.data);
events.addEventListener("user-joined", (event) => {   // named events, sent as "event: user-joined" in the stream
  console.log("joined:", JSON.parse(event.data));
});
events.onerror = () => console.log("connection lost — EventSource is already retrying on its own");

If the connection drops, EventSource automatically retries on its own — no manual reconnect logic needed at all, which is the entire trade EventSource makes for giving up the "client can send things too" half of WebSocket.

Rule Need the client to send data too? WebSocket. Only need the server pushing updates —live scores, a notification feed, streaming a long response? EventSource is simpler and reconnects itself for free.

Long polling

async function poll(attempt = 0) {
  try {
    const response = await fetch("/api/updates?wait=30");   // server HOLDS this request open until there's something to say
    if (!response.ok) throw new Error("HTTP " + response.status);
    handleUpdate(await response.json());
    poll();   // success: ask again immediately — the "long" part is the server delaying its response, not the client waiting
  } catch {
    setTimeout(() => poll(attempt + 1), backoffDelay(attempt));   // failure: back off instead of hammering a server that's down
  }
}
poll();

backoffDelay is built at the end of this chapter — the same wait-longer-after-each-failure rule applies to every reconnect strategy here, long polling included.

Ordinary polling means asking every N seconds regardless of whether anything changed. Long polling flips who waits: the client asks, and the server simply doesn't answer until it has something to say (or a timeout passes) — one request, held open, instead of hundreds of empty ones. It predates both options above and is strictly worse than either when they're available — but it's plain HTTP, so it still works through the rare restrictive proxy or old environment that blocks a WebSocket upgrade or doesn't handle a held-open SSE stream well.

Deciding between the three

WebSocket SSE / EventSource Long polling
Directionboth waysserver → client onlyboth, but request/response shaped
Reconnects itselfno — build it yourselfyes, built init's just fetch — call it again
Good fitchat, multiplayer, collaborative editinglive feeds, notifications, progress updatesfallback when neither above is available
Overheadlowest per message, once connectedlow, plain HTTPa full request every cycle

Reconnecting without hammering the server

Reconnecting instantly, every time, in a loop is exactly how a client turns a brief server hiccup into a self-inflicted denial of service against that same server the moment it comes back. The fix is the same shape as the retry pattern from a few chapters back: wait longer after each consecutive failure.

function backoffDelay(attempt, base = 500, max = 30000) {
  const exp = Math.min(base * 2 ** attempt, max);   // doubles each attempt, capped
  return Math.round(exp / 2 + Math.random() * (exp / 2));   // jitter: random within the top half of the range
}

for (let attempt = 0; attempt < 6; attempt++) {
  console.log("attempt", attempt, "->", backoffDelay(attempt), "ms");
}

Run it — the delay roughly doubles each attempt, then flattens out at the cap. The randomness (jitter) matters as much as the growth: without it, every client that dropped at the same moment (a server restart takes the whole fleet down at once) reconnects at exactly the same moment too, arriving as a synchronized thundering herd instead of a spread-out trickle.

let attempt = 0;
function connect() {
  const socket = new WebSocket(url);
  socket.onopen = () => { attempt = 0; };   // reset the counter once a connection actually succeeds
  socket.onclose = () => {
    setTimeout(connect, backoffDelay(attempt));
    attempt++;
  };
}
connect();
Interview answer, one sentence

"Polling asks repeatedly, Server-Sent Events push text one way and reconnect on their own, and WebSockets are two-way but need their own reconnection with backoff and jitter — pick the simplest that fits, and always plan for the connection dropping."

Before you move on
  • List the four readyState values in order, and say why a socket can never go backward through them.
  • Explain why EventSource reconnects automatically but a plain WebSocket does not.
  • Explain how long polling differs from ordinary interval polling, and why it's a fallback rather than a first choice.
  • Implement exponential backoff with jitter, and explain why the jitter matters as much as the doubling.
  • Given a feature — chat, a live notification feed, a fallback for a restrictive proxy — pick the right one of the three and justify it.

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 8 testsExponential backoff, cappedintermediate
  • 8 testsTrack reconnect attempts, and know when to give upintermediate
All 33 for this level →
I8

Finished reading?

0 of 41 chapters read in this series

→Next stop

  1. I7Previous · I7Error handling & debugging
  2. I8you are here
  3. Next · I9Offline & storagelocalStorage is fine for a theme or a small cache. Past a few MB, it stops being enough.7 min read

01On this page

5 sections · 0%

  1. 01WebSocket
  2. 02Server-Sent Events (EventSource)
  3. 03Long polling
  4. 04Deciding between the three
  5. 05Reconnecting without hammering the server

[] chapters · t top