Send Deltas, Not Full State, in Real-Time Apps
Broadcasting full state on every change kills real-time apps. Send deltas: only what changed. Learn when diffs beat snapshots and how to keep clients in sync.
A real-time app that broadcasts the full state of an object every time anything about it changes is signing up for a bandwidth and CPU bill that grows with the size of your data, not the size of your changes. Change one field on a large record and you ship the whole record to everyone watching. Do that on every keystroke, every drag, every tick, across a room of viewers, and you have built something that feels fast with ten items and dies with ten thousand. The fix is to send deltas: only what changed, described precisely enough that each client can apply it to the copy it already holds. Send diffs, not snapshots, and your traffic tracks activity instead of data size.
Why full-state broadcasts do not scale
The appeal of sending full state is that it is simple. The client does not have to merge anything; it just replaces its copy with the new one. That simplicity is exactly what makes it expensive. The payload is the size of the whole object regardless of how small the change was. A one-character edit to a document sends the whole document. A single status change on a large record sends every field. Multiply by the number of changes per second and the number of clients receiving them, and you are moving enormous volumes of data to communicate tiny amounts of information.
It gets worse under exactly the conditions where real-time matters most. Fast collaboration means many small changes in quick succession. Full-state broadcast turns that into a firehose of large messages, which then hits the slow clients hardest and drives the backpressure problems I described in handling backpressure from slow clients. The bandwidth waste and the buffer-blowup risk are the same root cause: you are sending far more than the change actually contains.
What a delta is, and what it requires
A delta describes the change itself: this field became this value, this item was inserted at this position, this row was removed. The client holds its current copy and applies the delta to reach the new state. The payload is proportional to the change, not the object, so a one-field edit is a tiny message no matter how big the object is.
The requirement deltas impose is that clients start from a known base and stay in order. A delta only makes sense relative to a specific prior state, so the client must have the right starting point and apply changes in the correct sequence. That is why deltas lean on the same ordered, gap-free delivery I argued for in guaranteeing message order: apply two field updates out of order and you land on the wrong value. Order the deltas and the client always reconstructs exactly the server's state.
You also need a way to establish the base. A client that just connected has no copy to apply deltas to, so you send it one full snapshot to start, then deltas from there. That snapshot-then-deltas pattern is the standard shape: one full state to bootstrap, small diffs to stay current. It is also how you recover a client that fell too far behind, hand it a fresh snapshot and resume deltas, which ties into resuming after a reconnect.
When full state is actually fine
Deltas are not free complexity, so do not cargo-cult them everywhere. If your objects are small, or changes are rare, full-state broadcast is simpler and the bandwidth difference is noise. A settings object that changes twice a day does not need a delta protocol; just send the whole thing. The case for deltas gets strong when objects are large, changes are frequent, or the audience is wide, because that is when the multiplier hurts.
There is also a middle ground worth knowing. For state that gets superseded, like a live metric or a cursor position, you do not even need true deltas; you need coalescing, sending only the latest value and dropping the intermediates, which I covered for live dashboard backends. The distinction: deltas accumulate to rebuild a growing structure like a document; coalescing collapses a series of values where only the newest matters. Pick based on whether history is part of the state or not.
Let the platform carry it, keep the protocol clean
The delivery of deltas, in order, with a snapshot bootstrap and a resume path, is real-time infrastructure. The definition of what a delta looks like for your data is application logic. Keep those separate. I define the diff format for my objects in the app and push the deltas through the real-time layer of AltoHost, which handles ordered delivery and reconnection so I do not rebuild that under every feature. For the wider picture of what belongs to the platform versus the app, I laid it out in the backend real-time apps actually need.
The gut check is to watch your payloads while making a trivial change. If editing one field ships kilobytes because you sent the whole object, you are on the full-state path and it will not scale. If it ships a few bytes describing exactly what changed, you are sending deltas and your bandwidth will track your activity, which is exactly what you want. Send the change, not the thing that changed. That is the whole idea.