Skip to content
ermalbujupaj.dev
All posts

Jul 8, 2026 · 2 min read · iOS

Confirmed state: building UI that never lies

What a home-security app taught me about optimistic UI — and why the pending state deserves to be designed first.

Optimistic UI is one of those patterns we apply on autopilot. Tap the like button, fill the heart immediately, sync later. For most consumer apps that's the right call — the cost of being briefly wrong is nothing.

Then I built an app for a home-security company, and the autopilot had to be switched off.

When "probably armed" is a bug

The app lets customers arm and disarm their security system remotely. Now ask: what should the screen show in the two seconds between the user tapping Arm and the system actually confirming it?

The optimistic answer — show it as armed — is a lie with consequences. If the command fails (network, hardware, a door left open), the user walks away believing their home is protected. The pessimistic answer — show nothing — reads as a broken app and invites a second tap, a second command, and a state machine in the weeds.

The answer that works is to make the pending state a first-class citizen:

enum SystemState {
    case disarmed
    case arming(requestedAt: Date)
    case armed(confirmedAt: Date)
    case failed(ArmError)
}

The UI renders exactly what the system knows. arming gets its own visual treatment — spinner on the shield, controls locked, honest copy: "Arming…". Only a confirmation from the backend moves the state to armed. Only then does the shield fill in.

Design the pending state first

The habit I took away from this project: when an action mutates something that matters, design the in-flight state before the success state. It forces the right questions early —

  • What does the user see while we wait?
  • What happens on timeout, and who retries — the user or the app?
  • Can two commands overlap, and which one wins?

If those questions are awkward to answer, the feature isn't understood yet. The pending state is where the real complexity lives; the success state is just the reward screen.

The general rule

Optimistic UI is a spectrum, and the dial is set by one question: what does it cost the user to see a state that turns out to be wrong?

  • A like that didn't stick: nothing. Be optimistic.
  • A message that didn't send: mild. Be optimistic, with a visible retry.
  • A security system that isn't actually armed: everything. Confirm first.

Most of our apps live at the cheap end of that spectrum, which is why the habit forms. But the discipline of the expensive end — honest pending states, explicit failure paths, state machines you can draw on a whiteboard — makes the cheap end better too.

  • #swiftui
  • #architecture
  • #ux