Robust WebSocket Reconnection Strategies in JavaScript With Exponential Backoff

WebSockets enable real-time communication between clients and servers, but handling connection drops gracefully is critical. In this guide, we’ll build a reconnection strategy using exponential backoff with jitter, ensuring both reliability and server-friendliness.

Why You Need a Reconnection Strategy

Network issues, server restarts, or even browser tab inactivity can cause disconnections. Without a reconnection system, your app can break silently or hammer the server with repeated attempts.

Step 1: Basic WebSocket Wrapper

class ReconnectingWebSocket {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.maxAttempts = 10;
    this.attempt = 0;
    this.connect();
  }

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

    this.ws.onopen = () => {
      console.log('WebSocket connected');
      this.attempt = 0;
    };

    this.ws.onmessage = (msg) => {
      console.log('Received:', msg.data);
    };

    this.ws.onclose = () => {
      console.warn('WebSocket closed. Reconnecting...');
      this.reconnect();
    };

    this.ws.onerror = (err) => {
      console.error('WebSocket error:', err);
      this.ws.close();
    };
  }

  reconnect() {
    if (this.attempt >= this.maxAttempts) {
      console.error('Max reconnection attempts reached.');
      return;
    }

    const delay = this.getBackoffDelay(this.attempt);
    console.log(`Reconnecting in ${delay}ms`);

    setTimeout(() => {
      this.attempt++;
      this.connect();
    }, delay);
  }

  getBackoffDelay(attempt) {
    const base = 500; // 0.5 second
    const max = 30000; // 30 seconds
    const jitter = Math.random() * 1000;
    return Math.min(base * 2 ** attempt + jitter, max);
  }

  send(data) {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(data);
    } else {
      console.warn('WebSocket not connected');
    }
  }
}

Step 2: Usage

const socket = new ReconnectingWebSocket('wss://yourserver.com/ws');

setInterval(() => {
  socket.send(JSON.stringify({ type: 'ping' }));
}, 5000);

Step 3: Best Practices

  • Use exponential backoff with jitter to avoid thundering herd issues.
  • Debounce UI actions that rely on socket availability.
  • Store queued messages if you need to send during offline periods.
  • Consider application-level ping/pong to detect dead sockets early.

Advanced Additions

  • Monitor connection health using setInterval and timeouts.
  • Integrate with Redux or a global state to reflect socket status in UI.
  • Add authentication token refresh logic on reconnect.

Conclusion

By implementing a solid reconnection strategy with exponential backoff and jitter, your WebSocket-based applications become more resilient and production-ready. These improvements reduce user disruption and protect your backend from overload during outages.

If this post helped you, consider supporting me: buymeacoffee.com/hexshift