BuildNot yet confirmed elsewhere1 publisher3 min readPublished
The WebSocket That Lies: Why Liveness Detection Beats Reconnect Logic
A dev.to write-up argues the failure that hurts production clients is the socket still reporting OPEN with no data arriving. Reconnect code never runs if nothing declares the connection dead.
The Engineer · Build desk
What happened
- A WebSocket that throws an error is easy to handle; the dangerous case is the connection that goes quiet, where the TCP connection is technically still open, the socket object still reports readyState === OPEN, and no data is arriving, so the application believes it has a live feed.
- The author encountered this failure while building a perpetual futures grid trading system whose bot consumes a live exchange market feed over WebSocket and makes order decisions from it.
- A dropped feed the client does not notice means the bot is reacting to a price snapshot that stopped updating minutes ago, while every layer of downstream risk management assumes the feed is current.
- The same failure surface applies to any product built on a live feed, including market data, order-status streams, chat, collaborative editing, IoT telemetry, and live dashboards.
- Detecting a dead connection is one of four things that go wrong in production: onclose does not always fire, and a silently half-open socket can sit there for minutes.
Why it matters
A dev.to post on WebSocket reconnection makes a point worth taking seriously: the failure you can handle is the one that throws, and the failure that hurts you is the one that goes quiet, with the TCP connection technically open, the socket object still reporting `readyState === OPEN`, and no data arriving [1]. That inverts where most client effort goes, because the backoff-and-replay machinery everyone writes is downstream of a decision nobody makes: declaring the connection dead.
The author's example is a perpetual futures grid trading bot that consumes a live exchange market feed over WebSocket and places orders from it [4]. A feed that stops updating without the client noticing means the bot is reacting to a price snapshot that went stale minutes ago, while every downstream risk check assumes the data is current [5]. The same failure surface applies to order-status streams, chat, collaborative editing, IoT telemetry, and live dashboards [6].
The post names the core mistake plainly: trusting `readyState`, which reports what the local socket object believes rather than whether packets are moving [3]. A NAT table entry that expired, or a phone switching from Wi-Fi to cellular, leaves the socket in `OPEN` with nothing coming through [3]. `onclose` does not always fire, and a silently half-open socket can sit there for minutes [c5a].
The proposed fix is an application-level heartbeat: send a ping on an interval, expect a pong within a deadline, and if the pong does not arrive, tear the connection down yourself rather than waiting on a TCP timeout that may be minutes away [10]. The sample implementation is deliberately small. A `setInterval` sends the ping and arms a `setTimeout` death deadline; any inbound pong clears that timer; if it expires, an `onDead` callback fires and the socket's own claim to be OPEN is ignored [14].
The sizing argument is the part to copy. RFC 6455 defines native ping/pong frames and the Node.js `ws` library exposes them, but browsers give no access to control frames, so browser clients send an application-level heartbeat message instead [11]. Either way, the timeout must be shorter than any upstream idle limit you know about; load balancers commonly cut idle connections at 60 seconds, and the post suggests a 20-second interval with a 10-second timeout [12]. That puts worst-case detection at roughly 30 seconds, half the idle limit it is meant to beat [15].
The second detail is the one that explains why teams get away with bad heartbeats for a long time. Any inbound traffic is proof of life, so on a firehose delivering market data every few milliseconds a missed pong is nearly impossible [13]. The heartbeat earns its keep during quiet periods, when a feed with nothing to say is indistinguishable from a feed that has died [13].
What to check in your own client: whether anything at all measures time since last inbound message, and whether that threshold is set below your load balancer's idle cut [12]. Then the three problems that follow detection, which the post lists and most demos skip: reconnecting without a thundering herd, since ten thousand clients returning at once will knock the server over again [c5b]; restoring subscriptions, because a fresh socket has none and the server has forgotten who you are [c5c]; and the gap, because messages published during the outage are gone unless you do something about it [c5d].
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence45
- Adoption
- Insufficient
- Hype gap+12
- Incentives22
- Confidence52
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
A WebSocket that throws an error is easy to handle; the dangerous case is the connection that goes quiet, where the TCP connection is technically still open, the socket object still reports readyState === OPEN, and no data is arriving, so the application believes it has a live feed.
ReportedSupportedSource: dev.to post 'WebSocket Reconnection Done Right: Backoff, Jitter, and Replay'2 sources— create a free account to open themView cited source - [2]
Detecting a dead connection is one of four things that go wrong in production: onclose does not always fire, and a silently half-open socket can sit there for minutes.
- [3]
The core mistake is trusting readyState, which tells you what the local socket object believes rather than whether packets are actually moving; a connection dropped at the network layer, such as an expired NAT table entry or a phone switching from Wi-Fi to cellular, can leave the socket in OPEN with nothing coming through.
- [4]
The author encountered this failure while building a perpetual futures grid trading system whose bot consumes a live exchange market feed over WebSocket and makes order decisions from it.
- [5]
A dropped feed the client does not notice means the bot is reacting to a price snapshot that stopped updating minutes ago, while every layer of downstream risk management assumes the feed is current.
- [6]
The same failure surface applies to any product built on a live feed, including market data, order-status streams, chat, collaborative editing, IoT telemetry, and live dashboards.
- [7]
Reconnecting without a thundering herd is a second production problem: if ten thousand clients all reconnect the instant the server comes back, they knock it over again.
- [8]
Restoring state after reconnect is a third problem: a fresh socket has no subscriptions and the server has forgotten who the client is.
- [9]
The gap during disconnection is a fourth problem: messages published during the outage are gone unless the client does something about it.
- [10]
The recommended fix is an application-level heartbeat: send a ping on an interval and expect a pong back within a deadline; if the pong does not arrive, declare the connection dead and tear it down rather than waiting for a TCP timeout that may be minutes away.
- [11]
RFC 6455 defines native ping/pong frames and the Node.js ws library exposes them directly, but in the browser the WebSocket API gives no access to control frames, so clients send an application-level heartbeat message instead; the logic is the same either way.
- [12]
The heartbeat is a liveness check rather than a keepalive, and its timeout should be set shorter than any known upstream idle timeout; load balancers commonly cut idle connections at 60 seconds, so a 20-second interval with a 10-second timeout catches trouble long before that.
- [13]
Any inbound traffic is proof of life: with a firehose of market data arriving every few milliseconds a missing pong is nearly impossible, so the heartbeat earns its keep during quiet periods when a feed with nothing to say looks identical to a feed that has died.
- [14]
The sample attachHeartbeat implementation uses setInterval to send a ping and arm a setTimeout death deadline, clears the pending death timer on any inbound pong, and calls an onDead callback when no pong arrives in time, explicitly ignoring the socket still reporting OPEN.
- [15]
A 20-second ping interval with a 10-second pong timeout gives a worst-case detection latency of about 30 seconds, half the 60-second idle cut common on load balancers.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toWebSocket Reconnection Done Right: Backoff, Jitter, and Replay
1 article · August 21, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.