100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Web Development

WebSockets in the Browser

A practical guide to the native browser WebSocket API — connecting, sending and receiving messages, handling reconnection, and working with binary data efficiently.

ImplementationBeginner8 min readJul 10, 2026
Analogies

The Browser WebSocket API

Every modern browser exposes a native WebSocket constructor as part of the DOM API, requiring no library: new WebSocket(url) opens a connection and returns an object exposing onopen, onmessage, onerror, and onclose event handlers (or you can use addEventListener). Because it's a browser primitive rather than a fetch-style request/response call, the connection persists across the page's lifetime until explicitly closed, the tab navigates away, or the network drops.

🏏

Cricket analogy: It's like a stadium giving every spectator a free earpiece tuned permanently to the commentary channel the moment they sit down, no separate radio purchase needed, unlike buying a program booklet each over.

Connecting, Sending, and Closing

Opening a connection is asynchronous: the socket's readyState starts at CONNECTING (0), moves to OPEN (1) once onopen fires, and you must wait for that state before calling send() or the browser throws an InvalidStateError. Messages sent from the browser can be strings or binary data (ArrayBuffer or Blob), and close(code, reason) lets you optionally pass a numeric close code so the server-side close handler can distinguish a normal shutdown from an abnormal one.

🏏

Cricket analogy: It's like a batsman who must wait for the umpire's 'play' signal before facing a ball; swinging at a delivery bowled during a mid-over stoppage would be pointless, just as sending before OPEN fails.

javascript
const socket = new WebSocket('wss://api.example.com/live');

socket.addEventListener('open', () => {
  console.log('Connected');
  socket.send(JSON.stringify({ type: 'subscribe', channel: 'orders' }));
});

socket.addEventListener('message', (event) => {
  const payload = JSON.parse(event.data);
  console.log('Received:', payload);
});

socket.addEventListener('close', (event) => {
  console.log(`Closed: code=${event.code} reason=${event.reason}`);
});

socket.addEventListener('error', (err) => {
  console.error('Socket error', err);
});

// Later, when the user navigates away from the live view
function disconnect() {
  socket.close(1000, 'user navigated away');
}

Handling Events and Reconnection

The browser API does not auto-reconnect; if the network drops, onclose fires and the client is responsible for deciding whether and how to reconnect, typically with exponential backoff plus jitter to avoid a thundering herd of clients all retrying at the exact same moment after a server restart. A robust client also tracks whether the close was clean (event.wasClean) versus abrupt, since a code like 1006 (abnormal closure) signals the connection died without a proper close frame and warrants a retry, while codes like 1000 (normal) usually shouldn't trigger one.

🏏

Cricket analogy: It's like a broadcast van losing satellite uplink mid-over; a good production team doesn't slam back on air instantly but waits a beat, checks the signal, then resumes, rather than every van in the country reconnecting at the exact same second.

Libraries like reconnecting-websocket wrap the native API to add automatic exponential backoff, jittered retry delays, and message queuing during disconnects, which saves you from re-implementing this logic in every app that needs a resilient client.

Binary Data and Performance

By default incoming binary frames arrive as Blob objects, but setting socket.binaryType = 'arraybuffer' switches them to ArrayBuffer, which is usually preferable for performance-sensitive code (e.g. parsing a binary protocol or feeding data into a TypedArray) since Blobs require an extra async read step before you can inspect the bytes. High-frequency updates (like live cursor positions or price ticks) should also be throttled or batched client-side, because flooding the DOM with a render on every single message can visibly janky a UI even though the socket itself easily keeps up.

🏏

Cricket analogy: It's like a scoring app choosing to receive raw ball-by-ball data as a compact binary feed rather than verbose text commentary, so it can update the scoreboard instantly instead of parsing sentences first.

Rendering directly inside every onmessage callback for a high-frequency stream (dozens of messages per second) can overwhelm the browser's layout and paint cycle. Batch incoming updates with requestAnimationFrame or a small debounce buffer so the DOM only repaints once per frame, not once per message.

  • The browser's native WebSocket API requires no library and is available globally via the WebSocket constructor.
  • Connections are asynchronous; you must wait for readyState OPEN (fired via onopen) before calling send().
  • The browser never auto-reconnects — clients must implement their own retry logic, ideally with exponential backoff and jitter.
  • event.wasClean and the numeric close code distinguish a normal shutdown from an abnormal one like 1006.
  • Setting binaryType = 'arraybuffer' avoids the extra async read step Blob requires for binary payloads.
  • High-frequency message streams should be batched or throttled before touching the DOM to avoid jank.
  • close(code, reason) lets the client signal intent so the server can distinguish clean vs abrupt disconnects.

Practice what you learned

Was this page helpful?

Topics covered

#WebDevelopment#WebSocketsStudyNotes#WebSocketsInTheBrowser#WebSockets#Browser#WebSocket#API#StudyNotes#SkillVeris#ExamPrep