FrontendLesson 3 of 48 min

Optimistic updates, or lying well

Showing the result before it is confirmed, and cleaning up when you were wrong.

When you tap a heart icon and it fills instantly, the server has almost certainly not confirmed anything yet. The interface assumed success and drew it. This is an optimistic update, and it is why good apps feel instant despite the same network as everyone else.

The optimistic flow
  1. Act
    Update the screen immediately, assuming success.
  2. Send
    Tell the server in the background.
  3. Confirm
    Usually fine. Nothing visible changes.
  4. Or revert
    It failed. Put it back and say so.

The honest middle ground is often better than either extreme: show the change immediately, but mark it as pending, slightly faded, with a small indicator. The user gets instant feedback and an accurate picture, and a failure is a change of state rather than a contradiction.

What to remember

  • Optimistic updates trade accuracy for perceived speed.
  • Only use them where failure is rare and reverting is harmless.
  • The revert path is the one that will be untested. Test it deliberately.

Terms in this lesson

Field notes

Loaded from a deliberately slow source. The lesson above was already readable while this was still travelling. That is streaming, and it is the same trick a chat interface uses.

The bug was three caches deep

A team spent two days on a "deploy that did not work". The deploy was fine. A CDN rule was caching the HTML document itself for an hour, so every visitor kept getting the previous build. The fix was one header; finding it needed knowing the copies existed.

Reported by a solo founder, 2024

Nobody had opened the empty state

A product launched with a dashboard that looked superb with demo data and completely blank on day one. Every new signup saw an empty white rectangle and assumed the app was broken. Signup-to-activation doubled after a two-hour fix.

Common enough to be a cliché

resolved in 901ms · region iad1

Hide field notes toggles a search param the loader reads. With it off, the slow promise is never created, so nothing streams.