C.W.K.
Stream
Lesson 07 of 08 · published

Reconnection Strategy

~14 min · browser, reconnect, backoff

Level 0Poller
0 XP0/60 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

WebSocket has no auto-reconnect

Unlike EventSource, the native browser WebSocket does not reconnect when the connection drops. This is intentional: reconnection policy depends on your application — chat reconnects aggressively; a one-shot streaming export does not. The protocol stays out of your way.

Exponential backoff with jitter

The industry standard is exponential backoff with jitter. After each failure, wait min(base * 2^retry, cap) seconds; add a randomized jitter of ±25% so 10,000 clients reconnecting simultaneously do not pile onto the recovering server in lockstep ("thundering herd"). Reset the retry counter on the next successful open.

What to skip on close 1000

Do not reconnect on a clean close (code 1000) — the application asked for it. Do reconnect on 1001, 1006, 1011, and most 4xxx codes. For 4001 (auth expired), prompt for re-auth instead of blindly reconnecting.

Code

ReconnectingWebSocket — minimal but correct·javascript
class ReconnectingWebSocket extends EventTarget {
  constructor(url, { maxRetries = Infinity, baseDelay = 1_000, capDelay = 30_000 } = {}) {
    super();
    this.url = url;
    this.maxRetries = maxRetries;
    this.baseDelay = baseDelay;
    this.capDelay = capDelay;
    this.retry = 0;
    this.shouldReconnect = true;
    this._connect();
  }

  _connect() {
    this.ws = new WebSocket(this.url);

    this.ws.addEventListener('open', () => {
      this.retry = 0;
      this.dispatchEvent(new Event('connected'));
    });

    this.ws.addEventListener('message', (e) => {
      this.dispatchEvent(new MessageEvent('message', { data: e.data }));
    });

    this.ws.addEventListener('close', (e) => {
      this.dispatchEvent(new CloseEvent('disconnected', { code: e.code, reason: e.reason }));

      const isCleanShutdown = e.code === 1000 || e.code === 4001;
      if (!this.shouldReconnect || isCleanShutdown) return;
      if (this.retry >= this.maxRetries) return;

      const base = Math.min(this.baseDelay * (2 ** this.retry), this.capDelay);
      const jitter = base * 0.25 * (Math.random() * 2 - 1);
      const delay = Math.max(0, base + jitter);

      this.retry += 1;
      setTimeout(() => this._connect(), delay);
    });
  }

  send(data) {
    if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(data);
  }

  close(code = 1000, reason = '') {
    this.shouldReconnect = false;
    this.ws?.close(code, reason);
  }
}
Why jitter — the thundering herd·text
  Server crashes at t=0.
  10,000 clients all set their next reconnect for t = 1s, 2s, 4s, 8s, ...
  Without jitter, all 10,000 hit the recovering server at exactly t=1s.
  -> Server immediately overwhelmed and crashes again.

  With ±25% jitter:
  Reconnects spread across the 0.75-1.25s window, then the 1.5-2.5s window.
  Server sees a smooth ramp instead of a wall of traffic.

External links

Exercise

Implement the ReconnectingWebSocket class above. Force the server to drop the connection every 5 seconds. Observe in devtools that retries happen at ~1, 2, 4, 8, 16 seconds with jitter, and that the counter resets after a successful reconnect.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.