Published Jan 25, 2026
Fast Realtime Updates Without WebSockets
Realtime features often tempt teams into complex architecture. You start by wanting a status indicator to update automatically. You end up maintaining a fleet of WebSocket servers and a Redis PubSub cluster.
We wanted snappy updates without the operational headache. We chose HTTP long polling combined with snapshot semantics.
The Problem With WebSockets
WebSockets introduce state. You have to manage persistent connections, handle backpressure, and debug a separate transport stack. Load balancers become more difficult to configure. Autoscaling gets tricky.
For most applications, this is overkill.
Long Polling Done Right
Long polling differs from standard polling. The client requests an update, and the server holds the request open until data changes or a timeout occurs.
This approach feels instant because the server responds immediately when an event happens. It works over standard HTTP, requires no special infrastructure, and is naturally stateless.
Snapshots, Not Streams
We do not stream individual events. When a change occurs, the backend returns a minimal snapshot: “The Approval object with ID 123 changed.”
This payload tells the frontend what is stale. The frontend then decides how to handle it—usually by re-fetching the relevant data.
This simplifies the logic. You do not need to replay a sequence of events to rebuild state. You just mark the cache as dirty.
Precision Cursors
To track state, we use high-precision timestamps. Every response includes the timestamp of the latest event. The client sends this timestamp back in the next request.
This ensures we never miss an update. If the client disconnects, it reconnects with its last known cursor, and the server sends everything that happened in the meantime.
Invalidation as a Primitive
The frontend does not apply patches. It invalidates queries.
If an “Account” event arrives, the frontend invalidates all account-related queries. If a “Comment” event arrives, it invalidates the comment list.
This keeps the UI consistent. We add a new model, define its invalidation rules, and the realtime system just works.
Handling Edge Cases
Realtime systems are full of sharp edges. We handle them explicitly:
- Timeouts: If the request times out (usually after 30 seconds), the client immediately sends a new one.
- Disconnects: If the server shuts down, it closes the connection cleanly. The client reconnects.
- Empty State: The first request returns a cursor so the polling loop can start.
Summary
You can build excellent realtime experiences without WebSockets.
Long polling with precise cursors and cache invalidation provides 90% of the benefit with 10% of the complexity. The UI feels alive, and the operations team sleeps at night.