Optimistic UI
Immediately updating the UI to show the expected success state before the server confirms the action, then rolling back if the request fails.
Plain English
When you like a tweet, the heart turns red instantly — it does not wait 200ms for Twitter's server to confirm. That is optimistic UI. The interface "assumes" the operation will succeed and updates immediately, creating an experience that feels instant and responsive. If the server returns an error, the UI quietly rolls back to the previous state and shows an error message. This pattern works best for actions that almost always succeed: likes, saves, reorders, form submissions with good client-side validation. The opposite approach — waiting for server confirmation before updating — is called pessimistic UI, and it makes every action feel sluggish, even on fast connections.
Technical
Implementation pattern: (1) Capture the current state as a rollback snapshot. (2) Immediately apply the optimistic state update to local state (useState/Zustand/Redux). (3) Fire the async API call in the background. (4) On success, optionally reconcile with the real server response (e.g., replace temp ID with real DB ID). (5) On failure, restore the rollback snapshot and surface an error. In React: `const prev = items; setItems(optimistic); try { await api.update(); } catch { setItems(prev); showError(); }`. React Query and SWR both have built-in `onMutate`/`onError` hooks for this pattern. For list reorders (drag-and-drop), update the array immediately and persist in the background. Key constraint: only use optimistic UI for low-stakes, reversible, idempotent-ish actions — never for payment processing or destructive deletes without explicit confirmation.
Live Demo
Optimistic UI
The UI updates immediately on click (optimistic). After 1.5s, the server responds — 80% success, 20% rollback with a shake animation.
Click to like
What's happening
Optimistic update
UI updates immediately — no wait
Server request in flight
User doesn't wait for this
Server response
Confirm or rollback
Optimistic UI trades perfect accuracy for perceived speed — rollback handles the rare failure.
Usage
✓ Good usage
A bookmark button that flips to "Saved" instantly on click, persists in the background via API, and reverts to "Save" with a brief "Failed to save" toast if the network request fails.
✗ Bad usage
Applying optimistic UI to a "Delete Account" action — immediately hiding the account page before server confirmation means there is no graceful rollback path if the deletion fails or is rejected.
Recommended values
- Apply optimistic state within the same event handler, before async call
- Store rollback: const prev = structuredClone(currentState)
- React Query: onMutate for optimistic update, onError for rollback
- Show subtle loading indicator if request takes > 1s (stale state warning)
- On error: restore prev state + show toast with retry option
- Avoid for: payments, destructive actions, low-confidence validations
Common mistakes
- Not storing a rollback snapshot before applying the optimistic state — you cannot revert what you did not capture.
- Using optimistic UI for payment or auth flows — failed rollbacks in those contexts cause data integrity issues.
- No error feedback on rollback — silently reverting state leaves users confused about what happened.
- Letting optimistic state diverge permanently from server state — always reconcile with the server response to catch edge cases.
AI Prompt Example
Copy this into Claude, Cursor, Bolt, or v0.
Implement optimistic UI on the Like button. On click: immediately toggle `liked` boolean and increment/decrement `likeCount` in local state. Save a `previousState` snapshot first. Fire `PATCH /api/posts/:id/like` in the background. On error: restore `previousState` and show a toast "Could not update — please try again." On success: optionally sync the server-returned count to catch discrepancies.