Optimistic Updates: Elevating Perceived Performance in Frontend Applications

Optimistic updates provide a powerful approach to enhancing the perceived performance of applications by anticipating successful operations and updating the user interface (UI) before server confirmation. This article delves into the efficient implementation of optimistic updates using React, React Query, and SWR, and discusses how they can significantly improve user experience (UX).

ShareShare

Optimistic Updates: Elevating Perceived Performance in Frontend Applications

In the realm of frontend development, the user's perception of an application's performance is often more impactful than the actual server response time. This perception, or perceived responsiveness, is what truly defines the user experience. One way to enhance this perceived responsiveness is through the use of optimistic updates.

Optimistic updates, as the name suggests, optimistically assume that a requested change in the application will be successful, and consequently, the UI is updated immediately to reflect this anticipated success. The network request associated with the change is sent concurrently, and if the server response contradicts the optimistic assumption, the UI is rolled back or patched accordingly.

Consider the ubiquitous 'Like' button as a classic example of optimistic updates in action. When a user clicks the 'Like' button on a post, the UI immediately reflects the like - the heart icon fills up instantly, and the 'Like' count increments. The server call associated with the 'Like' action runs in the background, almost invisible to the user.


The Performance Enhancement Potential of Optimistic Updates

While optimistic updates do not technically reduce the actual latency of server responses, they significantly improve the perceived latency from the user's perspective. By immediately reflecting the anticipated success of an action in the UI, optimistic updates eliminate the frustrating wait times often associated with server responses. This leads to a smoother, more responsive user experience that is not marred by annoying loading spinners or unresponsive interfaces, even under suboptimal network conditions.


Implementing Optimistic Updates: A Deep Dive

The manual implementation of optimistic updates in React involves the use of the useState hook to manage the state of the UI element in question. For instance, consider the case of a 'Like' button:

const [liked, setLiked] = useState(false);

const handleLike = () => {
  setLiked(true); // Optimistic UI Update

  fetch("/api/like", { method: "POST" })
    .then(res => {
      if (!res.ok) throw new Error();
    })
    .catch(() => {
      setLiked(false); // Rollback upon unsuccessful server response
    });
};

In this example, the useState hook is used to maintain the liked state of the post. The handleLike function, triggered when the 'Like' button is clicked, optimistically sets the liked state to true, immediately reflecting the like in the UI. The corresponding network request to the server is sent concurrently. If the server response indicates an error, the catch block is executed, rolling back the optimistic update by setting the liked state back to false.

While this manual implementation approach works, it necessitates the explicit handling of error cases and rollback logic.

To streamline this process, libraries like React Query provide more efficient solutions. React Query provides the useMutation hook, which can handle rollbacks, deduplicate queries, and perform background validations, significantly simplifying the implementation of optimistic updates.

Here is how the 'Like' button example could be implemented using React Query:

const mutation = useMutation(likePost, {
  onMutate: async (newData) => {
    await queryClient.cancelQueries("likes");
    const prevLikes = queryClient.getQueryData("likes");

    queryClient.setQueryData("likes", (old) => [...old, newData]);
    return { prevLikes };
  },
  onError: (err, newData, context) => {
    queryClient.setQueryData("likes", context.prevLikes);
  },
  onSettled: () => {
    queryClient.invalidateQueries("likes");
  },
});

In this implementation, the onMutate method is called before the mutation, allowing you to make optimistic updates to the UI and cancel any outgoing queries related to the data being mutated. If the mutation fails, the onError method reverts the UI to its state prior to the mutation. After the mutation either succeeds or fails, the onSettled method is used to invalidate and refetch any queries related to the mutation.

Another library that provides a more declarative approach to implementing optimistic updates is SWR. Here's how the previous example could be implemented using SWR:

mutate("/api/cart", async (cart) => {
  const updated = [...cart, newItem];
  await fetch("/api/cart", { method: "POST", body: JSON.stringify(newItem) });
  return updated;
}, { optimisticData: [...data, newItem], rollbackOnError: true });

In this code snippet, the mutate function is used to perform the optimistic update, with the optimisticData option providing the new state for the UI, and rollbackOnError ensuring that the UI is reverted to its previous state in the event of an error.


Real-World Applications of Optimistic Updates

Several popular applications leverage the power of optimistic updates to enhance their user experience:

  • Twitter: Actions such as liking, retweeting, and bookmarking tweets are reflected instantly in the UI, with the server sync happening in the background.
  • Notion: Changes made to documents in Notion are reflected instantly in the UI, even before the changes are sent to the server.
  • Figma: Changes made by multiple users are optimistically applied and then synced over a WebSocket.

Best Practices and Anti-Patterns

While optimistic updates can greatly enhance the user experience, they should be used judiciously. They are best suited for actions that are unlikely to fail, such as liking a post, saving a form, adding items to a cart, or toggling settings.

However, for actions with potentially severe consequences, such as making payments or deleting data, or in situations where the server state is highly volatile, optimistic updates can lead to inconsistencies between the UI and the server state. Therefore, in such cases, it is advisable to wait for the server response before updating the UI.

Another potential pitfall to avoid is not handling failures properly. If a server response contradicts an optimistic update and the UI is not rolled back correctly, it can lead to an inconsistent and confusing user experience. Similarly, using optimistic updates with destructive actions, such as deletion, or updating multiple pieces of state with no undo mechanism, can lead to irreversible mistakes.


Conclusion: Harnessing the Power of Optimistic Updates

Optimistic updates provide a powerful technique for improving the perceived performance of your application. By optimistically predicting the success of actions and reflecting these predictions immediately in the UI, they can make your application feel instantaneous.

However, while optimistic updates can significantly enhance the user experience, they also require careful handling of failure cases and rollback logic to ensure consistency between the UI and the server state. As such, they should be used judiciously and responsibly.

In the world of frontend development, delivering a fast, responsive user experience is often about perception. And optimistic updates, when used correctly, can go a long way in delivering the feeling of speed to your users.

Get the series as it ships

New parts of Agent Access Control, plus analysis of agent identity standards as they move. Roughly monthly. No filler, no cadence promises I won't keep.

about

Ehsan Hosseini

Ehsan Hosseini

me [at] ehosseini [dot] info

I work on what AI agents are allowed to do inside companies, and whether you can prove who authorized it. I write about it here, and build it at my company.

How it works?

I write what I learn while building systems that decide what an AI agent is allowed to do. The thinking, the arguments, and the calls about what's true are mine. I use AI to draft, structure, and speed up research, the same way I'd use any other tool. It doesn't decide what I believe.

I check technical claims against primary sources before a post goes out: the specs, the RFCs, the original documentation, not a model's memory of them. When I'm uncertain, I say so. When something is opinion rather than fact, I mark it as one. If I get something wrong, that's on me.

© 2026 Ehsan Hosseini. All rights reserved.