Why Connections Need Heartbeats
TCP alone does not reliably tell an application when a peer has silently vanished — a laptop that goes to sleep, a mobile device that loses signal in a tunnel, or a NAT gateway that drops an idle mapping can leave a socket that both operating systems still consider technically open, even though no data will ever pass again. WebSocket defines ping (opcode 0x9) and pong (opcode 0xA) control frames specifically so either endpoint can proactively check whether the other side is still responsive, rather than waiting for the OS-level TCP keepalive timers, which are often minutes long and disabled by default in many environments.
Cricket analogy: It's like a fielding captain periodically checking in with a bowler who's gone quiet over the radio earpiece — the captain needs an active confirmation of readiness rather than assuming everything's fine just because no complaint has come through.
How Ping and Pong Frames Work
A ping frame may carry up to 125 bytes of arbitrary application data, and a compliant peer must respond as soon as practical with a pong frame echoing that exact same payload back, unless it has already queued a close frame. Either side can send an unsolicited pong at any time as a one-way heartbeat signal without waiting for a ping first, which some implementations use to keep intermediary proxies from timing out an idle connection. Most server frameworks and reverse proxies (like a WebSocket-aware load balancer) will send pings on an interval and close the connection if no pong arrives within a configured timeout, since a missing pong is the clearest signal that the peer is actually gone rather than just quiet.
Cricket analogy: It's like a bowler shouting 'How's that?' and expecting the umpire to raise a finger in direct response — if the umpire doesn't answer at all within a reasonable time, everyone on the field knows something's badly wrong.
// Node.js server using the 'ws' library
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
function heartbeat() {
this.isAlive = true;
}
wss.on('connection', (ws) => {
ws.isAlive = true;
ws.on('pong', heartbeat);
});
// Every 30s, ping clients and terminate any that didn't pong last round
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on('close', () => clearInterval(interval));Heartbeats Above the Protocol Layer
Not every environment gives applications access to raw ping/pong control frames — browser JavaScript, for instance, cannot send a WebSocket ping frame directly through the standard API, so many web apps implement an application-level heartbeat instead: sending a small text frame like {"type":"heartbeat"} on a timer and expecting a matching reply within a deadline, then reconnecting if it doesn't arrive. This approach also plays better with intermediaries that only forward data frames reliably, and it lets the heartbeat carry useful metadata such as a client timestamp for latency measurement, at the cost of a few extra bytes compared to the protocol-level control frames.
Cricket analogy: It's like a domestic league using its own local scoring app to track overs bowled when the official international scoring system isn't licensed for that ground, achieving the same goal through a workaround built on top.
Choose your heartbeat interval carefully: too frequent wastes bandwidth and battery on mobile clients; too infrequent means dead connections linger and consume server resources (and mislead any 'online user count' feature) longer than necessary. 20-30 seconds is a common middle ground, tuned against known intermediary idle-timeout values.
- TCP alone cannot reliably detect a silently vanished peer, so WebSocket defines ping (0x9) and pong (0xA) control frames.
- A compliant peer must respond to a ping with a pong echoing the same payload, up to 125 bytes.
- Unsolicited pongs can be sent as one-way heartbeats without a preceding ping.
- Servers commonly ping clients on an interval and terminate any that fail to pong within a timeout.
- Browser JavaScript cannot send raw ping frames directly, so many web apps implement application-level heartbeat messages instead.
- Application-level heartbeats can carry extra metadata like timestamps for latency measurement.
- Heartbeat interval is a tradeoff between fast dead-connection detection and unnecessary bandwidth/battery use.
Practice what you learned
1. Why is TCP's own connection state insufficient for detecting a dead WebSocket peer?
2. What must a compliant WebSocket endpoint do upon receiving a ping frame?
3. Can a pong frame be sent without a preceding ping?
4. Why do many browser-based web apps implement heartbeats using regular text frames instead of protocol-level ping/pong?
5. What is a reasonable consideration when choosing a heartbeat interval?
Was this page helpful?
You May Also Like
Opening and Closing Connections
How a WebSocket connection is established through the HTTP upgrade handshake and gracefully terminated using the closing handshake.
Close Codes and Error Handling
What WebSocket close codes mean, how to choose the right one, and how to build robust reconnect and error-handling logic around them.
Text and Binary Frames
How WebSocket messages are broken into frames, and the difference between UTF-8 text frames and raw binary frames.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics