Race conditions in UI state

Slap yourself if

You think JavaScript being single-threaded means your UI state can’t race.

Why this exists

UI state races exist because modern frontends are event-driven, async by default, and optimistic about ordering. Promises, effects, network responses, and user input all resolve independently, while state is treated as if it were linear.

Mental model

UI state is a timeline, not a value. If you don’t control who’s allowed to write at each moment, the last resolver — not the correct one — wins.

  • Multiple async operations capture different snapshots of state.
  • Responses resolve out of order relative to user intent.
  • State updates apply without validating freshness or relevance.
  • The UI reflects a valid state transition — just not the intended one.
  • Stale network responses overwriting newer user actions.
  • Effects re-running with outdated closures.
  • Optimistic updates not being reconciled correctly.
  • Loading flags that lie about what’s actually in flight.

Race conditions in UI state occur when multiple asynchronous state updates resolve out of order, causing the UI to reflect stale or invalid transitions despite each update being logically correct in isolation.

  • Boolean loading flags shared across requests
  • State updates that don’t encode intent or versioning
  • Assuming request order equals resolution order
  • Ignoring cancellation or invalidation paths

Deep dive

Requires Pro

Premium deep dives include more internals, more scars.

Why async UI updates race even without threads

How small races turn into UX corruption

Why most state abstractions leak races

Debugging races that refuse to reproduce

When naive async state is unacceptable