Software Architecture

Realtime Applications: What They Actually Require

Realtime is not a feature you add. It changes your connection model, your state model, your deployment and your failure modes. What to plan for before you promise it.

A server holding persistent live connections to several clients

The short version

  • Realtime is not a feature you bolt on. It replaces the request-response assumption your whole stack is built around.
  • The hard parts are not the sockets. They are connection state, fan-out across servers, reconnection correctness and backpressure.
  • Most products described as realtime need fresh, not instant. Those are very different engineering bills.
  • Design the reconnect path first. It is the code that runs most often in production and gets tested least.

Somewhere between the demo and the roadmap, “it should update live” becomes a requirement. It sounds like a frontend concern. It is not. Adding realtime to an application changes how you hold connections, how you shape state, how you deploy, and what happens when things go wrong.

This article is about what to plan for before you commit to it.

Decide what “realtime” means here

The word covers a range of requirements with very different costs. Before designing anything, establish which one you are actually building.

Requirement Tolerance Typical answer
Eventually fresh Seconds to a minute Polling on an interval, or a refresh button
Promptly updated 1–5 seconds Server-sent events, or short polling
Interactive Under 200 ms, bidirectional WebSockets
Collaborative Under 200 ms, with conflict resolution WebSockets plus CRDTs or operational transforms

The distinction matters enormously to cost. A dashboard that refreshes every ten seconds and a collaborative editor are separated by an order of magnitude in engineering effort, and both get described as realtime in the same planning meeting.

Ask what breaks if the update is thirty seconds late. If the honest answer is “nothing, the user would just be mildly annoyed,” you need freshness, not realtime, and you can have it this sprint instead of next quarter.

The connection model changes everything

A conventional web application holds no state between requests. Any server can handle any request, scaling is a matter of adding instances behind a load balancer, and a deploy is a rolling restart nobody notices.

Persistent connections break all three of those properties.

Connections are now stateful and long-lived

Each connected client occupies a socket, some memory and a slot in whatever accepts connections on your server. A conventional application handling a thousand requests per second might have only a handful open at any instant. The same application with a thousand connected users holds a thousand open sockets continuously, whether or not anyone is doing anything.

This is a different capacity planning problem. You are now sizing for concurrent connections rather than requests per second, and the runtime you chose matters much more: threaded servers that allocate a thread per connection run out of headroom far earlier than event-driven or fibre-based ones.

A message must reach the right server

Here is the problem that surprises teams the most. A user connects and their socket lands on server A. Later, a background job on server B needs to notify that user. Server B has no connection to them.

The standard answer is a shared pub/sub backplane. Every server subscribes to the channels relevant to its connected clients, publishers write to the backplane, and it fans out. Redis pub/sub is the usual first choice; NATS and dedicated message brokers appear as scale grows. Managed services such as Pusher or Ably exist precisely to sell you an escape from this problem, and for many teams that is a rational purchase.

Whichever route you take, note what you have added: a new piece of infrastructure that is now on the critical path for a user-visible feature, with its own failure modes and its own capacity limits.

Deploys become disruptive

Restarting a server drops every connection it holds. With a thousand connected clients across four servers, a rolling deploy disconnects and reconnects everyone, and if your clients reconnect immediately and simultaneously you have built yourself a thundering herd that arrives just as the new instances are still warming up.

The mitigations are known but must be deliberate: exponential backoff with jitter on the client, connection draining on the server so clients are asked to leave rather than dropped, and deploy windows chosen with connection count in mind.

The state model changes too

In request-response, the server sends the current state and the question of what the client previously knew never arises. With push, it is the central question.

Events or state?

You can push the change (“item 47 moved to done”) or the new state (“here is the current board”). Events are small and efficient; they also require the client to have every prior event to be correct, which makes any dropped message a silent divergence. State snapshots are larger and idempotent; a client that missed three updates is still correct after the next one.

The pragmatic pattern most mature systems converge on is to push small events for responsiveness, include a sequence number, and have the client request a full snapshot whenever it detects a gap.

Reconnection is the hard part

Connections drop constantly in the real world — phones change networks, laptops sleep, corporate proxies time out idle sockets. Reconnection is not an edge case; it is the most frequently executed path in a realtime system.

What the client needs to do on reconnect:

  • Back off exponentially, with jitter, so a server restart does not produce a synchronised stampede.
  • Re-authenticate, because the token may have expired while disconnected.
  • Re-subscribe to the channels it cared about.
  • Reconcile missed state, either by replaying from a last-seen sequence number or by fetching a fresh snapshot.
  • Tell the user honestly what is happening. A silently stale interface is worse than a visible “reconnecting” indicator, because the user trusts what they see.

Backpressure

What happens when you generate updates faster than a client can consume them? On a slow mobile connection, an unbounded send queue grows until the server runs out of memory. Options are to drop intermediate updates and send only the latest, to coalesce updates within a time window, or to disconnect clients that fall too far behind and let them resynchronise from a snapshot. All three are acceptable. Having no policy is not.

Authorisation is harder than it looks

In request-response, permission is checked per request against fresh data. With a long-lived subscription, permission was checked once — possibly hours ago.

Two problems follow. First, revocation: a user removed from a project keeps receiving its updates until something forces a re-check. Second, filtering: a channel carrying updates for an entire organisation will happily deliver records to a user entitled to see only some of them, unless filtering happens at fan-out.

Both are solvable, and both need designing in rather than discovering. Re-verify authorisation periodically on long-lived subscriptions, and scope channels narrowly enough that channel membership is the authorisation decision.

Testing and observability

Standard request-response test tooling does not cover any of this. What you need in addition:

  • Integration tests that connect real clients, exercise disconnect and reconnect, and assert that state converges.
  • Load tests measured in concurrent connections rather than requests per second.
  • Metrics for connection count, message rate, fan-out latency and queue depth per connection — the last one is your early warning for backpressure problems.
  • Deliberate chaos: kill a server while clients are connected and confirm they recover without user intervention.

Boolean Solutions experience with realtime

We have built live dashboards, collaborative tools and notification systems, and the pattern we see most often is a team that solved the happy path in a fortnight and then spent three months on reconnection, fan-out and authorisation. The work is real, it is predictable, and it is far cheaper to plan for than to discover.

The other thing we do regularly is talk teams out of it, because the requirement was freshness and polling would have delivered it in an afternoon. If you are weighing that decision, get in touch.

Further reading

Written by

Udit Mittal

Founder at Boolean Solutions. Twenty years of building and rescuing web, mobile and AI products for SaaS companies and startups — and writing down what actually worked.

Get in touch