Show it now, confirm it later
In a request-and-wait app, tapping “like” or “mark done” sends a request and the interface changes only when the answer comes back. On a slow network that is a visible pause; with no network it is an error.
An optimistic update reverses the order. The app applies the change on screen at once, sends the request in the background, and deals with the answer when it arrives. When the request succeeds, the person never notices there was a network involved. The work is in handling the cases where the answer is no.
The usual sequence
Client libraries for the web describe nearly the same steps. In TanStack Query’s guide for its cache, the app:
- Cancels any refetch in progress, so a late response cannot overwrite the optimistic value.
- Takes a snapshot of the current value.
- Writes the new value into the cache, which redraws every screen that uses it.
- Sends the change to the server.
- On error, restores the snapshot. Either way, it refetches afterwards so the screen ends on the server’s real state.
TanStack also documents a lighter variant that leaves the cache alone. While the request is pending, the interface renders the submitted value as a temporary item, for example at reduced opacity, and the item disappears when the request settles. If it fails, the app can keep showing it with a retry button.
Apollo Client keeps the optimistic value apart from the real one. When a mutation starts, its cache stores a separate optimistic version of the object rather than overwriting the existing cached version, so the cached data stays correct if the guess was wrong. When the server responds, Apollo removes the optimistic version and writes the server’s values; if the mutation returns an error, it discards the optimistic version and rolls back to the previous state.
React’s useOptimistic hook works at the component level. It shows a temporary value while an action is in progress, and if the action fails, the interface returns to the value it showed before.
Speculative until confirmed
The server does not have to agree with the guess. It may hold newer data, apply a rule the device does not know, or compute a different result. Replicache’s documentation calls client-side results speculative and server results canonical: changes run locally first, are pushed to the server and run again there, and the server’s result takes precedence when the client pulls it back. To combine the two, Replicache rewinds to the last state it received from the server, applies the server’s changes, and then replays any changes still pending on top.
This is the general shape of reconciliation. The confirmed state is the base, and pending local changes are reapplied on top of it, so the person still sees their unconfirmed work.
What changes when the app is offline
On a working connection an optimistic update lasts only as long as one request. In an offline-first app it can last for days, which raises the stakes:
- The pending change must survive a restart. Keep it in a persistent queue, an outbox, not only in memory. Android’s guide calls this pattern a lazy write: write to the local store first, then queue the change for the network.
- Retries must be safe. A change sent, interrupted, and sent again must apply once, which needs a stable identifier per change.
- Other edits arrive meanwhile. By the time the change is sent, someone else may have changed the same record, so the app needs a rule for sync conflicts.
- Status must be visible. A change shown as done for a day and then rejected is worse than a short spinner, unless the person could see it was pending. Offline Protocol’s documentation recommends separate indicators for locally saved work, peer delivery, application acceptance, and backend commit, and a recovery action when something is rejected. Delivered, accepted, committed explains why these differ.
When not to be optimistic
Some actions should wait for an answer. Android’s guide gives a bank transfer as an example of an online-only write: the app should disable the action offline or tell the person it failed, not pretend it worked. The same goes for claiming the last seat, changing permissions, or anything where a later reversal would mislead someone who has already acted on it.
A useful test is to ask what happens if the change is rejected an hour after the person saw it succeed. If the honest answer is a short note and a retry, an optimistic update is fine. If the answer is a missed flight or a double sale, wait for the server.