Skip to the notes
JSGroundwork
JSGroundwork handwritten · web dev
✎Playground→⌘Problems↻Review🔥Progress

Chapters

27 chapters
⌕
Beginner9›
B1Setup & mental modelB2Types & valuesB3Operators & flowB4Functions (first half)B5Objects & arrays (first half)B6DOM & eventsB7Basic asyncB8Errors & tools★Cheat page
Intermediate10›
I1Scope & functions, properlyI2Objects deeplyI3Prototypes & OOPI4Async, properlyI5Modules & toolingI6Regex, dates & APIsI7Error handlingI8Real-time connectionsI9Offline & storage★Cheat page
Advanced10›
A1Engine & memoryA2Advanced asyncA3MetaprogrammingA4Types & dataA5Patterns & architectureA6PerformanceA7SecurityA8EcosystemA9Testing★Cheat page
/ search[ ] chaptert top

JavaScript levels

1Beginner2Intermediate3Advanced

Ready to read

JSJavaScript⑂Git◎Interview prepΣDSA in JSSDSystem Design
More topics15›
</>HTML{ }CSS⚛ReactNNext.jsNeNest.jsTSTypeScriptNoNode.js🐳DockerDBSQL & Databases✓Testing🔒Web Security☁Cloud & DevOps◈GraphQL◆Redis☸Kubernetes
100%
I8

Real-time connections

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

Nothing below is a .try block — 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).

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() {
  try {
    const response = await fetch("/api/updates?wait=30");   // server HOLDS this request open until there's something to say
    const data = await response.json();
    handleUpdate(data);
  } finally {
    poll();   // immediately ask again — the "long" part is the server delaying its response, not the client waiting between requests
  }
}
poll();

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();
Practice this layer

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

Exponential backoff, capped5 tests · intermediateTrack reconnect attempts, and know when to give up2 tests · intermediate
←previousError handling↑ CovernextOffline & storage→