Heartbeats
In the last lesson we said the silent slide from live to dropped is the transition apps forget. This lesson explains why it is silent — and gives you the one tool that breaks the silence: a heartbeat.
A quiet connection and a dead one look the same
Section titled “A quiet connection and a dead one look the same”Here is the uncomfortable truth about an open socket: doing nothing looks exactly like being broken. If neither side has sent a message for thirty seconds, there are two possible explanations, and from the inside they are indistinguishable:
- Everything is fine and nobody simply had anything to say.
- The connection died ten seconds ago — a Wi-Fi handoff, a sleeping laptop, a load balancer that culled an idle stream — and the packets announcing its death never arrived.
TCP itself does not rescue you here. TCP only learns a connection is broken when it tries to send and the acknowledgements never come. If your app is idle, TCP is idle too, so it never tries, so it never finds out. The operating system’s built-in TCP keepalive can take hours to fire by default — useless for a real-time app. The result: your WebSocket object happily reports readyState === 1 (OPEN) over a connection that has been rubble for minutes.
The only cure is to make traffic on purpose at a known interval and check that it comes back. That is a heartbeat.
Two kinds of heartbeat
Section titled “Two kinds of heartbeat”There are two distinct mechanisms, and people muddle them constantly.
- Protocol ping/pong frames. The WebSocket protocol defines special control frames: a ping frame and a pong frame. By spec, when an endpoint receives a ping it must reply with a pong. These live below your message handlers — they are part of the framing layer covered in the foundations module.
- Application-level heartbeats. Ordinary messages your code sends and answers, like a JSON
{"type":"ping"}your server replies to with{"type":"pong"}.
Why ever build the app-level version when the protocol gives you ping/pong for free? Because the browser does not let you use the protocol frames. The browser’s WebSocket API has no ping() method and fires no event when a ping or pong arrives — they are handled invisibly inside the browser. A browser server can send protocol pings to the browser, but browser client code cannot send or observe them. So in practice:
- Server to browser: the server may use real protocol ping/pong to probe the browser.
- Browser to server: the client must use app-level messages, because that is all it can see.
Most production systems therefore run an app-level heartbeat so the same logic works on every platform. That is what we build below.
The heartbeat loop
Section titled “The heartbeat loop”The shape of an app-level heartbeat is always the same:
sequenceDiagram
participant C as Client
participant S as Server
loop every interval
C->>S: ping
S-->>C: pong
Note over C: pong seen — mark connection healthy
end In code it is an interval timer plus a small piece of state: send a ping every N seconds, and remember whether the matching pong came back. Here is the heart of it as plain TypeScript:
const HEARTBEAT_MS = 5000;
function startHeartbeat(ws: WebSocket) { const timer = setInterval(() => { if (ws.readyState !== WebSocket.OPEN) return; ws.send(JSON.stringify({ type: 'ping', t: Date.now() })); }, HEARTBEAT_MS);
ws.addEventListener('message', (event) => { const msg = JSON.parse(event.data as string); if (msg.type === 'pong') { // The connection answered — it is genuinely alive right now. } });
ws.addEventListener('close', () => clearInterval(timer));}Sending the ping is the easy half. The valuable half — noticing when the pong never comes — is the entire next lesson. For now, focus on the rhythm: a steady pulse, and an answer for each beat.
A live ping/pong
Section titled “A live ping/pong”The demo below uses an in-page socket with the real WebSocket API. The mock plays the role of a well-behaved server: every time it receives an app-level ping message, it replies with a pong. The client sends a ping every second and logs the round trip. Watch the heartbeat settle into its rhythm.
You will see pings going out and pongs coming back, one pair per second. Each returned pong is positive proof the connection was alive at that instant — not thirty seconds ago, not “probably”. That is the whole point of a heartbeat: it converts silence, which tells you nothing, into a steady signal you can actually trust.