In this lesson, you will learn how to design a state reconciliation loop that brings your local client data back into alignment with the server after your application regains connectivity.
The Reconciliation Challenge
When a client goes offline, it may continue to mutate local data (optimistic updates). Meanwhile, the server state continues to evolve. When the connection is re-established, simply overwriting the server state with local data (or vice versa) results in lost work or race conditions. A reconciliation loop acts as the bridge that intelligently merges these divergent timelines.
Building the Loop
To reconcile, we treat the local state as a queue of "pending mutations" rather than a final source of truth. Each mutation should be assigned a sequenceID (as discussed in our previous lessons on sequencing). When the WebSocket reconnects, the client initiates a sync process:
- Identify Divergence: The client sends the
lastSeenSequenceIDto the server. - Server Replay: The server sends all missed delta updates that occurred after that sequence ID.
- Local Replay: The client applies its pending local changes (those not yet acknowledged by the server) on top of the server’s latest state.
async function reconcileState(localQueue, lastConfirmedId) {
// 1. Fetch missing updates from server
const missedUpdates = await socket.emit('get-missed-updates', { lastConfirmedId });
// 2. Apply missed updates to local state
missedUpdates.forEach(update => applyToLocalState(update));
// 3. Re-apply pending local changes in chronological order
localQueue.sort((a, b) => a.sequenceID - b.sequenceID);
localQueue.forEach(mutation => {
// Re-run the local mutation logic over the updated state
applyToLocalState(mutation);
socket.emit('sync-mutation', mutation);
});
}
Managing Conflicts
If a local mutation conflicts with a server update during this replay, you must apply your conflict resolution strategy (like "Last Write Wins" or "Server Wins"). By ensuring your local queue remains immutable until confirmed, you maintain a predictable state that can be replayed safely during reconnection events.
Key Takeaway: A successful reconciliation loop treats local offline changes as a ordered sequence of mutations that must be replayed against the server’s latest state upon reconnection.