Website loading

Optimistic Frontend Development

Blogpost-featured-image
publisher-image
Victor
Head of Web Development
Feb 15, 2022 • 6 min

In the world of web apps and mobile apps, there is a lot of planning, designing, and decision-making even mid-development. When you already have a complex system in place, new features can really break the app’s UX; or your code; or the app’s performance.

Let’s consider this example:

We need to implement a “favorite” feature on our posts. After saving a post as “favorite”, three other actions need to be made - triggering an email, a push notification, and updating the recommendation algorithm.
A “favorite” feature seems as easy to implement as it gets. But consider that the logic behind adding something to favorites can not only take a long time but also break after the user has already waited for the response. This is the point where we need to make a decision and it is as much a UX decision as it is an implementation one.

Many other scenarios can make you face the same decision. Things like having multiple microservices and the favorite action traversing quite a bit, or having an email trigger when someone favorites your post, etc.

To go a little bit deeper into this topic and have something visual to help you better understand the concept, I have also prepared a video here.

 

 

The app

Our app is very simple, but also very important to our customers (currently just me): it is a feed of meme templates. I was lucky enough to find Imgflip’s API and started coding a simple feed that fetches a number of meme templates, shows the title, image and also adds a favorite button for each post. At Wolfpack Digital, we mostly use Vue or Nuxt (here’s why), so I went with the same stack.

The like/favorite button was set up like a booby trap, as I always made the user wait for 3.5s, and after that, the action had a 50-50 chance to fail. This way, we were able to reproduce both successful and error behavior and see how our app would react in both these cases.

The natural first thought - that doesn’t really work

My first thought when implementing actions that might take a while (I think working with mobile apps has definitely influenced this a lot) is to add a loader and only update the UI when the action is successfully completed. If the action is quick, that is great, but what if the action is taking a long time? I will need to start thinking about blocking the user from pressing the same button again while it’s loading, also only “loading” a specific component/item, so the user’s UI does not get fully blocked from such a minor action.

Also, a significant issue would be error handling. The user hits the like button on a post, sees the loader, but decides to continue scrolling. After 2 seconds of only scrolling (or even worse, liking 10 other posts), an error message shows that the action failed. That would be confusing and frustrating.

Optimistic rendering - I got 99 problems but stopping the user ain’t one

Optimistic updates (a.k.a. Optimistic UI) is a pattern that we can especially see in frontend/UX development where different actions update instantly on the visual interface without waiting for confirmation from the server.

Github is one of the many companies that do this - I have seen similar examples from Twitter, Facebook, Instagram. When I hit the star icon, the UI automatically updates and the API call starts. But the UI is already changed as confirmed long before the response comes back as successful.

So let’s update our code! And actually, everything is pretty simple: instead of handling the UI update inside the .then after the await method that does the API call/action, we just update it before even calling. The only thing left to do now is error handling & updating the UI back if the action failed.

The result makes a lot more sense from a UX standpoint.

Error handling

You have spent the last few minutes reading about how we manipulate the UI and pre-fill everything assuming it would magically work. But we still need to set up some code to know if something failed.

The video below covers what I would consider as bad UX, but might be the best example of this pattern’s weakness: visually representing a failed action. The user usually moves on, scrolls further, and showing an error will just make everyone wonder which action actually failed and why they are getting out of their flow for something that happened a few seconds ago.

This does not mean that we shouldn’t handle errors, but we need to choose our behavior very carefully:

  • we could just log the error and never show the user visual updates in case of failure
  • another option would be a great error message (so we would know what action actually failed), but that is also very unobtrusive
  • giving the option to retry, like adding a small button for the certain area that failed
  • as soon as we find out that a call failed, update the UI back to the correct state; make sure no heart icon is left behind
  • silent retries are also an option, but there should be a limit

Important decision making

This pattern is not great in all situations. It replaces a bad user experience interaction - taking too long for interaction - with something that may be even worse, but happens a lot less. That’s why we need to choose wisely and be careful before going for it.

Of course, there are some good observations that we can take into consideration while going through the decision process:

  • avoid applying it for destructive/constructive actions - interactions that delete, create or edit an entry, especially if at the end of a long flow
  • this pattern works great with boolean actions - marking/removing something as favorite or liking/unliking
  • it is very important to apply this to reliable actions/API calls. For a small, binary action, but with a success rate of only 50%, this is definitely not a great option
  • the same applies if the API is always slow and takes a very long time to complete; we are trying to be optimistic, but it is not a great idea to let the user just move on and start a lot of actions, all with a successful UI update, before the previously called actions are confirmed as completed
  • sometimes a delay is expected (for example a change in permissions) and it is actually preferred to wait for confirmation before moving on

Conclusion

This pattern needs a very high level of communication with the design team, as it affects the UX of the whole app. The basic concept assumes a highly reliable and (hopefully) binary action will complete, updating the UI accordingly before the actual confirmation arrives.

Very similar optimistic patterns can be seen in multiplayer games or in concurrency (where multiple locks are managed with the calculated hope that they won’t interfere with each other).

I hope this article will help you make better decisions in the future. Also, if you’d like to keep learning, I suggest you check out this guide to understanding business logic as a QA specialist. You’ll find plenty of good tips on decision-making there as well!

web-development
frontend
fronted-development

tech insights & news

blog

Stay up to date with the tech solutions we build for startups, scale-ups and companies around the world. Read tech trends and news about what we do besides building apps.