Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    Designing Snapshot and Delta APIs

    The server's side of real-time sync: what a snapshot must carry, what a delta must say, how sequence numbers and resync work, and why edits should share the snapshot's shape.

    Growingtended planted 3 min read
    On this page

    I’ve written about consuming snapshots and deltas on the client: subscribe and buffer, load a snapshot, apply newer deltas, never overwrite what the user is editing (Snapshot and Delta Sync over WebSockets). Most of the pain I hit on the client in 2026, building trading dashboards at a fintech, traced back to decisions on the server. This note is the other side: what I’d ask of an API that streams live state.

    The snapshot

    A snapshot is the full current state of whatever the client is looking at. It should carry:

    • A sequence number or version. Without it the client can’t tell which deltas are already included. This is the single most important field.
    • Everything needed to render, including metadata that rarely changes (column definitions, units, precision), so the client doesn’t stitch together several calls before it can paint.
    • Stable identifiers for every entity, so deltas can target them.

    And it should be cheap to ask for again. Resync is a normal operation, not an emergency.

    The delta

    A delta says what changed since a version. It should:

    • Carry its own sequence number, strictly increasing per stream.
    • Express add, update and remove explicitly. Update-only streams leave ghost rows behind when something disappears.
    • Be granular. Send the fields that changed, keyed by entity id. If a tiny change to one field re-sends a large payload, every client re-renders far more than it should. I watched a whole screen re-render because a big snapshot changed by one value.
    • Be idempotent to apply. Applying the same delta twice should do no harm, because retries and reconnects happen.

    Gaps and resync

    Clients will miss messages: a reconnect, a sleeping laptop, a slow consumer. The protocol should make recovery mechanical:

    1. The client notices a gap in sequence numbers.
    2. It asks for a fresh snapshot (or for deltas since its last good version, if the server keeps a short history).
    3. It discards anything older than the new base and carries on.

    If the server can’t replay history, say so, and let the client fall back to a snapshot. That’s the same idea as a database replica catching up from a log, or rejoining a cluster in Raft.

    Snapshot vs delta: when to send which

    • Deltas when changes are small relative to the state and frequent. They save bandwidth and client work.
    • Periodic snapshots when state is small, or when deltas are hard to compute correctly. A snapshot is self-healing: any missed message is fixed by the next one.
    • Both, commonly: a snapshot on subscribe and on request, deltas in between, and perhaps a periodic snapshot as a safety net.

    Edits should look like snapshots

    When users can edit live data, the write path matters as much as the read path. The pattern that saved the most bugs: edits use the same shape as the snapshot, keyed by the same ids, and the server answers with the new version. Then the client can compare its pending edits to the latest snapshot field by field, and the server can validate an edit against a known version (rejecting it if the base has moved on, or merging if that’s safe). Designing for audit and deltas from the start (who changed what, from which version) makes both concurrency and debugging far easier.

    Backpressure on the server

    A server streaming to many clients has to decide what to do when one falls behind. Options: buffer (with a limit), conflate (send only the latest value per entity instead of every intermediate one), or drop the client back to snapshot mode. For prices and positions, conflation is usually right: a slow client needs the latest state, not every step in between. More on that in The Node.js Event Loop, Streams and Backpressure.

    Related: WebSockets · Server-Sent Events (SSEs) · Message Delivery Semantics · REST API Design Trade-offs: Bulk Deletes, Request Bodies and Cancellation

    • #websockets
    • #api-design
    • #real-time
    • #sequence-numbers
    • #event-streams

    ↪ Links to (7)