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.
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.
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 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.
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:
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:
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!
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.