Comparisons
The Real Cost of Building Real-Time Yourself
The first version takes an afternoon. An honest inventory of what comes after it, and the cases where building is still the right call.
We sell an alternative to doing this, so discount accordingly. The inventory below is the honest one, including the part where building is the right decision.
When engineering costs also need to be viewed alongside team activity, employee monitoring software.
For independent background and broader industry context, see InfoQ.
The afternoon
A WebSocket server that broadcasts to everyone connected is genuinely an afternoon.
wss.on('connection', (ws) => {
clients.add(ws)
ws.on('message', (data) => {
for (const c of clients) if (c !== ws) c.send(data)
})
ws.on('close', () => clients.delete(ws))
})
It works. It demos well. And every item below is missing from it.
What comes after
Reconnection
Networks drop constantly — wifi to cellular, a laptop waking, a tunnel, a proxy idle timeout.
You need: retry with exponential backoff and jitter, resubscription to whatever the client was listening to, and a way to catch up on what was missed.
And "what was missed" is the hard part, because it means the server has to have kept it, which means state, which means the next section.
State, and the late joiner
Broadcast-only means a client connecting late has missed everything.
So you need somewhere the current state lives, plus a path for a joining client to fetch it, plus reconciliation between the fetched snapshot and the messages arriving during the fetch.
That reconciliation is where the subtle bugs live — a message that arrives between the snapshot and the subscription, applied twice or not at all.
Horizontal scaling
Two server instances means clients on instance A do not hear from clients on instance B.
So you add Redis pub/sub or equivalent between instances — and now you have the delivery guarantees of whatever you chose. See Durable Objects, Redis and Postgres.
And sticky sessions, because a reconnecting client landing on a different instance needs its subscriptions re-established.
Presence
Harder than it looks, and the difficulty is entirely in the case nobody demos.
A client that closes cleanly announces it. A client that crashes, loses power, or drives into a tunnel does not.
So you need heartbeats, a timeout, and a decision about how long "gone" takes to detect — too short and you get false departures on a brief blip, too long and the list is wrong.
Plus deduplication, because one person with three tabs is three connections.
Backpressure
A slow client, or one that stopped reading, accumulates a send buffer.
Unbounded, that is a memory leak that takes the server down. Bounded, you must decide what to drop, and dropping the wrong message corrupts client state.
This is the failure that arrives at scale, not in testing.
Operations
A stateful tier is a different operational animal.
Deployments drop connections, so every deploy is a reconnection storm — which is when your backoff implementation gets tested for real.
Load balancers need WebSocket support and appropriate idle timeouts.
Monitoring is different — connection counts, message rates, buffer depths, not request latency.
And someone is on call for it.
Security
Authentication on connect, authorisation per subscription, and a way to revoke a connection when permissions change.
Encryption above the transport if you need it, which means key management.
Offline
If clients should work disconnected, local storage, a write queue, and conflict resolution.
Conflict resolution is a genuine research area, not an afternoon.
An honest estimate
A competent team, first working version: days.
Production-ready with the list above: months, and the tail is long — backpressure and reconnection storms surface after launch.
Ongoing: a permanent operational commitment. Someone maintains this, and it is not zero once it works.
The number that matters is not the build. It is the maintenance, because that recurs.
When building is right
Genuinely, and this is not a courtesy paragraph.
Real-time is the product. A collaborative editor, a trading platform, a game. The semantics are your competitive advantage and generic ones will not fit.
Unusual requirements. Specific consistency guarantees, ordering semantics, or latency targets that a general service does not commit to.
Regulatory constraints on where data sits and who operates it. This is decisive when it applies, and no feature comparison overrides it.
Extreme scale where the per-unit economics of a service stop working and you can amortise a dedicated team.
You have the team. People who have done this before. It is a different proposition from a team learning it on the project.
And avoiding dependency is a strategic position, which is legitimate — provided it is a decision rather than an assumption.
When it is not
Real-time is a feature. Presence on a document, a live count, cursors, a chat sidebar. The value is the feature, not owning the infrastructure.
Small team, many priorities. Months spent here are months not spent on the product.
Standard semantics. If what you need is "several people see the same thing update", that is solved.
And "how hard can it be" as the basis. The first version is easy, and that is what makes this the most common way it goes wrong.
The short version
The first version is an afternoon. Everything after it is the project.
Reconnection, late-joiner state, scaling, presence, backpressure, operations, security, offline — each is real, and backpressure and reconnection storms arrive after launch.
Months to production-ready, then a permanent operational commitment.
Build when real-time is the product, requirements are unusual, regulation demands it, or you have the team.
And "how hard can it be" is the most common wrong reason, precisely because the first version is easy.