Optimistic UI is one of the fastest ways to make a product feel responsive without reducing network latency at all. The interface updates immediately, then the server call catches up in the background. When it works, the experience feels sharp. When it fails badly, it feels dishonest.
That is why the architecture question is not "should we use optimistic updates?" It is "which mutations are safe enough to predict?"
A Good Optimistic Mutation
Low-risk actions with simple rollback are the best candidates:
- liking a post
- toggling a setting
- reordering a local list
- adding a draft item that can be removed cleanly
With TanStack Query, the pattern is explicit:
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async (nextTodo) => {
await queryClient.cancelQueries({ queryKey: ["todos"] });
const previous = queryClient.getQueryData<Todo[]>(["todos"]);
queryClient.setQueryData<Todo[]>(["todos"], (current = []) =>
current.map((todo) => (todo.id === nextTodo.id ? nextTodo : todo)),
);
return { previous };
},
onError: (_error, _nextTodo, context) => {
queryClient.setQueryData(["todos"], context?.previous);
},
});
The important part is not the optimistic write. It is the rollback path.
Where It Gets Risky
Optimistic UI becomes dangerous when:
- conflicts are common
- business rules live only on the server
- money or inventory is involved
- failure is hard to explain or undo
A payment confirmation should not be "optimistically successful." A social like probably can be.
Better Rule
Use optimistic updates where failure is rare, rollback is clear, and the user can understand what happened if the server rejects the change. That produces a fast product without teaching users to distrust the interface.
Further Reading