50 Interaction Patterns: Synchronization and Recovery
50.1 Start With the Decision
A thermostat app, wall unit, web page, and voice tool may show different states. Sync rules must settle which state is true.
50.2 Route Overview
This is part 2 of 2. Review Interaction Patterns: State and Feedback for the preceding evidence.
50.3 Learning Objectives
- Analyse Ecobee polling and state sync tradeoffs.
- Design recovery for stale, failed, and conflicting updates.
50.4 Chapter Roadmap
- Ecobee State Synchronization
- Checkpoint: Sync Implementation
- Common Mistakes
- Interaction Pattern Pitfalls
- Knowledge Check
- Quiz: Interaction Patterns
- Optimistic UI for Garage Doors
- Checkpoint: Recovery States
- State Sync Strategy Choices
- Pending vs Confirmed States
- Checkpoint: Safety and Accessibility
- Interactive Quiz: Match Concepts
- Interactive Quiz: Sequence the Steps
- Common Pitfalls
- Map to User Mental Models
- 2. Over-Relying on Icons Without Labels
- 3. Ignoring State Transition Feedback
- Label the Diagram
- Code Challenge
- Summary
- For Kids: Meet the Sensor Squad!
- Concept Relationships
- See Also
- In 60 Seconds
- Try It Yourself
- What’s Next
50.5 Ecobee State Synchronization
Ecobee’s smart thermostat (launched 2015) faced a state synchronization challenge that illustrates why distributed state management matters. The thermostat has four interfaces: the device’s touchscreen, the smartphone app, the web portal, and voice assistants (Alexa, Google Assistant). Early versions used a polling-based synchronization model where each interface checked the cloud for updates every 30 seconds.
Read this case as a state-lifecycle walkthrough. Establish the authoritative setpoint first, then follow one wall-unit change through the local display, cloud record, mobile and web views, and voice response. At each hand-off ask whether the value is pending, confirmed, stale, rejected, or queued offline. Next compare the polling delays with the later push path, and inspect how optimistic display, reconciliation, ordering, and conflict handling affect what each person sees. The design lesson is broader than lower latency: distributed controls need an explicit authority model, version or ordering evidence, visible intermediate states, and a recovery rule when interfaces disagree. Without those contracts, a fast interface can still present a convincing but obsolete value and invite a second command based on false state.
The problem in numbers:
| Interface | Sync Method | Latency | User Complaint |
|---|---|---|---|
| Touchscreen | Direct (local) | <100 ms | None |
| Mobile app | Cloud poll every 30s | 0-30 seconds | “App shows wrong temperature” |
| Web portal | Cloud poll every 60s | 0-60 seconds | “Outdated readings” |
| Voice assistant | On-demand API call | 2-5 seconds | “Alexa says 72 but screen says 68” |
When a user adjusted the temperature on the wall unit, the app could show stale data for up to 30 seconds. During that window, if someone else opened the app and adjusted the temperature based on the outdated reading, a “temperature war” ensued — the system oscillated between competing setpoints.
Ecobee’s resolution (2017 firmware update):
- Push-based sync via WebSocket: Replaced polling with persistent WebSocket connections. State changes propagate to all connected interfaces within 1-2 seconds.
- Optimistic UI with reconciliation: The app shows the new setpoint immediately when the user drags the temperature slider, with a subtle pulsing animation (“pending”) until the device confirms.
- Conflict resolution with timestamp: Each state change carries a millisecond-precision timestamp. Last-write-wins ensures deterministic behavior during simultaneous adjustments.
- Offline queue with merge: When connectivity drops, commands queue locally and replay in order on reconnection. If the device state has changed during the outage, the user sees a notification: “Temperature was changed while you were offline.”
Measurable outcomes:
- Cross-interface sync latency: 30 seconds reduced to 1.5 seconds average
- “Wrong temperature” support tickets: decreased 74%
- User-reported “temperature wars”: decreased 89%
- App session engagement: increased 23% (users trusted the displayed data)
Design lesson: For any IoT product with multiple control interfaces, push-based synchronization with optimistic UI is not optional — it is the minimum viable architecture. Polling creates a window of inconsistency that erodes user trust in every interface.
Checkpoint: Sync Implementation
You now know:
- Commands need ids so retries do not create duplicate physical actions; pending commands need a timeout path such as 10 seconds.
- Polling every 30 seconds or 60 seconds creates stale-interface windows; push-based WebSocket or MQTT updates can reduce cross-interface lag to about 1-2 seconds.
- Support evidence should use the UI vocabulary, so “timeout,” “offline,” “confirmed,” and “changed while offline” mean the same thing in logs and screens.
50.6 Common Mistakes
50.6.1 Mistake 1: Unclear State Indication
The Problem: Toggle switches and buttons that don’t clearly show current state, leaving users guessing.
Real Example: A smart plug app has a toggle labeled “Power.” When the toggle is to the right, does that mean the power is ON, or that tapping will turn it ON?
The Fix:
| Bad Design | Good Design |
|---|---|
| Toggle labeled “Power” | Status: “CURRENTLY ON” with button labeled “Turn Off” |
| Button labeled “Lock” | Status: “Unlocked” with button labeled “Lock Door” |
| Slider with no labels | “Brightness: 75%” with slider showing current value |
Design Principle: Show state (what is true now) separately from controls (what you can do).
50.6.2 Feedback for Delayed Actions
The Problem: Commands sent to IoT devices take 1-5 seconds due to network latency, but UI provides no feedback, leading users to tap repeatedly.
Real Example: User taps “Lock Door” in app. Nothing happens visually for 3 seconds. User taps again, thinking it failed. Door locks, then unlocks.
The Fix: Implement optimistic UI updates with loading states:
| User Action | Immediate Feedback (0-100 ms) | During Processing (1-5s) | On Success | On Failure |
|---|---|---|---|---|
| Lock door | Button shows “Locking…” | Spinner + greyed-out state | “Locked” (green) | “Failed to lock” + retry button |
| Set temperature | Display updates to new temp | “Sending to device…” | Temperature shows on device | “Device offline” + queue for later |
Design Principle: Acknowledge immediately (< 100 ms), show progress (1-5s), confirm completion, prevent double-submission.
50.7 Knowledge Check
Scenario: A smart garage door takes 8-12 seconds to fully open or close (physical mechanical operation). Network round-trip adds 200-500 ms latency. Users complained about unresponsive UI.
Without Optimistic UI (Original Implementation):
// BAD: User waits for server confirmation before UI updates
async function toggleDoor() {
showSpinner(); // Show loading spinner
const response = await fetch('/api/garage/toggle'); // 8-12 seconds total
if (response.ok) {
updateUI('open'); // Finally update UI
} else {
showError('Command failed');
}
}
User Experience: Tap button → Spinner for 8-12 seconds → UI updates. Users tap multiple times thinking it didn’t work, sending duplicate commands.
With Optimistic UI (Improved Implementation):
// GOOD: Immediate UI feedback with reconciliation
async function toggleDoor() {
const previousState = doorState; // Save current state
const targetState = (previousState === 'closed') ? 'opening' : 'closing';
// Step 1: Immediate optimistic update (< 100ms)
updateUI(targetState); // Show "Opening..." immediately
showProgressIndicator(); // Visual feedback: door animating
try {
// Step 2: Send command to device
const response = await fetch('/api/garage/toggle', {
method: 'POST',
body: JSON.stringify({ target: targetState })
});
if (response.ok) {
// Step 3: Poll for actual state confirmation
const finalState = await pollDoorState(12000); // 12s timeout
updateUI(finalState); // Confirm: "Open" or "Closed"
clearProgressIndicator();
} else {
// Step 4: Rollback on failure
updateUI(previousState); // Revert to previous state
showToast('Door command failed - check if door is obstructed');
}
} catch (error) {
// Network error - revert optimistically updated state
updateUI(previousState);
showToast('Connection lost - door may not have moved');
}
}
async function pollDoorState(timeout) {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const state = await fetch('/api/garage/state').then(r => r.json());
if (state === 'open' || state === 'closed') {
return state; // Final state reached
}
await sleep(1000); // Poll every 1 second
}
throw new Error('Timeout waiting for door state');
}
UI State Machine:
| Time | Displayed State | Actual Door State | Visual Indicator |
|---|---|---|---|
| T+0 ms | “Opening…” | Still closed | Animated progress bar |
| T+100 ms | “Opening…” | Starting to open | Progress bar at 10% |
| T+4s | “Opening…” | Halfway open | Progress bar at 50% |
| T+8s | “Opening…” | Fully open | Progress bar at 90% |
| T+8.5s | “Open” ✓ | Fully open | Green checkmark |
Error Recovery Scenarios:
| Error Condition | UI Response | User Communication |
|---|---|---|
| Network timeout | Revert to previous state + warning | “Connection lost - verify door state visually” |
| Door obstructed | Show “Opening…” then “Error” | “Door stopped - check for obstruction” |
| Duplicate command | Ignore if state matches | No change (already opening) |
| Door sensor failure | Show last known state + ”?” | “Cannot verify door state - sensor offline” |
Measured Results:
| Metric | Without Optimistic UI | With Optimistic UI | Improvement |
|---|---|---|---|
| Time to visual feedback | 8-12 seconds | < 100 ms | 99% faster |
| Duplicate command rate | 43% of users | 2% of users | 95% reduction |
| User satisfaction (SUS) | 52 (poor) | 78 (good) | +50% |
| Support calls about “broken button” | 28/month | 1/month | 96% reduction |
| Average taps per action | 2.7 | 1.02 | 62% reduction in duplicate commands |
Perceived Responsiveness Calculation:
Key Lesson: For any IoT action with > 500 ms latency, optimistic UI is not optional—it’s required for acceptable UX. The actual door operation time (8-12 seconds) didn’t change, but perceived responsiveness improved 240× by acknowledging the tap in 50 ms instead of making users wait 12 seconds for confirmation. Always provide immediate visual feedback, then reconcile with actual state asynchronously.
Checkpoint: Recovery States
You now know:
- For slow mechanical actions, show the transition state immediately, then confirm the final state after the device reports it.
- A failed command should revert or mark uncertainty, not leave the optimistic state looking final.
- Duplicate commands, obstruction, sensor failure, and network timeout need different messages because each asks for a different next action.
| Device Characteristic | Sync Strategy | Update Mechanism | Conflict Resolution |
|---|---|---|---|
| Always online (Wi-Fi smart bulb) | Push via WebSocket | Device publishes state changes immediately | Last-write-wins with timestamp |
| Intermittently online (battery sensor) | Poll + offline queue | App polls every 30s; device queues commands when offline | Merge queues on reconnection |
| Multiple physical controls (thermostat with wall dial + app) | Authoritative device state | All interfaces subscribe to device; device is source of truth | Device state overrides app optimistic updates |
| Low-latency required (smart lock) | Local mesh + cloud sync | BLE mesh for local control; cloud sync for remote access | Local control always wins |
| High-frequency updates (sensor data) | Throttle + aggregate | Device sends deltas only (> 0.5°C change); cloud aggregates | Latest value in 5-second window |
Synchronization Pattern Selection:
| Scenario | Pattern | Trade-offs |
|---|---|---|
| Smart home with app + voice + wall switches | Pub/sub via MQTT broker | All interfaces subscribe to devices/{id}/state topic. Device publishes on change. Low latency (< 500 ms), requires broker. |
| Industrial equipment with local HMI + cloud dashboard | Authoritative device with periodic cloud sync | Device maintains state; HMI reads local; cloud polls every 10s. Resilient to connectivity loss, cloud data may be stale. |
| Wearable with phone app | Bluetooth GATT with NOTIFY | Device notifies app on state change via BLE characteristic. Very low latency (< 50 ms), limited range. |
| Fleet management with thousands of vehicles | Event-sourcing with eventual consistency | Vehicles log state changes; cloud replays events to rebuild state. Scales to millions of devices, complexity in conflict resolution. |
Conflict Resolution Strategies:
| Conflict Type | Resolution Rule | Example |
|---|---|---|
| Simultaneous changes | Last-write-wins with server timestamp | User A sets temp to 70°F at 14:30:00, User B sets to 72°F at 14:30:01 → 72°F wins |
| Offline edits | Operational transform or CRDT | Device accumulates +3°C while offline, cloud has -1°C → Merge to +2°C net change |
| Physical override | Physical control always wins | User manually adjusts wall thermostat → App optimistic update is discarded |
| Safety-critical | Conservative merge (safer option) | Smoke detector armed remotely + locally disarmed → Armed state wins (fail-safe) |
| Shared-household preference | Named policy, not a hidden default | Two residents set different target temperatures → the system applies whichever a chosen policy names: last-command-wins, priority voting among linked accounts, or account-owner override |
The shared-household row is a different kind of conflict from the rows above it. Simultaneous-change and offline-edit conflicts are races between two commands that both look valid; the timestamp or merge rule settles them without a person needing to know a race happened. A shared-household preference conflict is a disagreement between two people who both have permission to act, and simply picking whichever command arrived last hides that disagreement instead of resolving it. A multi-user product should make the resolution policy visible in account or household settings — last-command-wins for a household that wants simplicity, a vote or shared-schedule negotiation for a household that wants consensus, or an admin/owner override for a rental or managed-property setup — rather than silently applying one rule and letting residents discover the policy through repeated conflict.
Anti-Pattern: Using polling-only synchronization with 30-60 second intervals. Creates inconsistency windows where users see stale data and make conflicting changes.
What Practitioners Do Wrong: Implementing optimistic UI that shows the new state identically to a confirmed state, leaving users unable to distinguish “command sent” from “command completed.”
The Problem: When a user taps “Lock Door,” the UI immediately shows a locked icon. If the command fails (network error, battery dead, door jammed), the UI still shows “locked” for several seconds until the error is detected. During this window, the user believes the door is locked when it’s actually unlocked—a security risk.
Real-World Example: A smart lock app used optimistic UI without status indicators. User testing revealed:
- 68% of users left their home within 10 seconds of tapping “Lock”
- Average error detection time: 4.2 seconds
- In error scenarios, users were already outside before the app showed “Failed to lock”
- Result: 34% of users in error scenarios did not return to manually check the door
The Correct Implementation — Visual State Machine:
// Define distinct visual states
const UIStates = {
UNLOCKED: {
icon: 'unlocked',
color: 'red',
label: 'Unlocked',
ariaLabel: 'Door is unlocked'
},
LOCKING: {
icon: '🔒',
color: 'yellow',
label: 'Locking...',
opacity: 0.6, // Dim to indicate pending
spinner: true, // Show activity indicator
ariaLabel: 'Sending lock command to door'
},
LOCKED: {
icon: '🔒',
color: 'green',
label: 'Locked ✓',
ariaLabel: 'Door is locked and confirmed'
},
ERROR: {
icon: '✗',
color: 'red',
label: 'Lock Failed',
ariaLabel: 'Door failed to lock - check manually'
}
};
async function lockDoor() {
setState(UIStates.LOCKING); // Optimistic: show pending state
try {
const response = await sendLockCommand();
if (response.confirmed) {
setState(UIStates.LOCKED); // Confirmed by device
} else {
setState(UIStates.ERROR);
showAlert('Door did not lock - verify manually');
}
} catch (error) {
setState(UIStates.UNLOCKED); // Revert on network error
showAlert('Command failed - door still unlocked');
}
}
Visual Distinction Techniques:
| State | Visual Indicator | Duration | User Understanding |
|---|---|---|---|
| Pending | Dimmed (60% opacity) + spinner | Until confirmed or timeout | “Command sent, waiting” |
| Confirmed | Full opacity + checkmark | Persistent | “Action complete” |
| Failed | Red X + error message | Until dismissed | “Action did not complete” |
Accessibility Requirements (WCAG 2.1):
- Don’t rely on color alone: Combine color (yellow/green/red) with text labels (“Locking…” vs “Locked”)
- Provide aria-live updates:
<div aria-live="polite" aria-atomic="true">Sending lock command...</div> - Use aria-busy attribute:
<button aria-busy="true">Locking...</button>while pending
Measured Impact of Proper State Indication:
| Metric | Without State Distinction | With Pending Indicators | Change |
|---|---|---|---|
| Users who noticed command failures | 32% | 91% | +184% |
| Average time to detect error | 8.4 seconds | 1.2 seconds | 86% faster |
| Users who verified door manually after error | 41% | 89% | +117% |
Key Lesson: Optimistic UI requires three distinct visual states: pending (optimistic), confirmed (reconciled), and failed (rolled back). Never show a pending state identically to a confirmed state, especially for safety-critical actions.
Checkpoint: Safety and Accessibility
You now know:
- Pending and confirmed states must look and read differently, especially for locks, alarms, pumps, heaters, and access control.
- Accessibility is part of the state machine: do not rely on color alone, use text labels, and announce important status changes.
- A muted notification, dismissed notification, acknowledged incident, and resolved event are separate states, so store and display them separately.
Common Pitfalls
Creating interaction flows that make sense to engineers but contradict users’ existing mental models from smartphones and web applications produces steep learning curves and abandonment. Map every primary interaction to an existing familiar pattern before inventing new paradigms.
Icon-only interfaces that appear clean in design reviews fail when users cannot identify what an icon means without trying it. Pair icons with text labels in primary navigation and reserve icon-only presentation for secondary or expert-level interactions where meaning is established.
Interactions that change device state (locking a door, arming a sensor) without immediate visual or auditory feedback leave users uncertain whether their action was registered, often triggering repeated taps. Acknowledge every state change with a clear animation, LED change, or sound within 200 ms.
50.8 Summary
This chapter covered essential interaction patterns for IoT interfaces:
Key Takeaways:
Optimistic UI: Provide immediate feedback (< 100 ms), show progress during network operations, reconcile on success/failure. State Synchronization: Device state is authoritative, all interfaces subscribe to updates, last-write-wins for conflicts. Notification Escalation: Five severity levels from silent logging to emergency alerts, with automatic escalation on non-response. Feedback Matching: Critical actions need immediate + haptic, background tasks need completion notifications.
Interaction patterns are the secret rules that make smart devices feel smooth and responsive!
50.8.2 Key Words for Kids
| Word | What It Means |
|---|---|
| Optimistic UI | Showing the result instantly (before it actually happens) so the app feels super fast |
| State Sync | Making sure ALL devices show the same information at the same time |
| Alert Fatigue | When you get SO many notifications that you ignore ALL of them, even important ones |
| Notification Escalation | Using quiet alerts for small things and loud alerts for important things |
How this chapter connects to other IoT concepts:
Builds on: Interface Design Fundamentals provides the UI component hierarchy foundation that interaction patterns operate within. Enables: Multimodal Design uses optimistic UI and state sync patterns across voice, touch, and gesture interfaces. Supports: Design Process & Checklists validation relies on proper implementation of feedback and state synchronization. Applied in: Communication Networks understanding network latency characteristics informs optimistic UI timing parameters. Privacy consideration: Privacy and Compliance distributed state synchronization affects data residency and GDPR compliance.
Related topics for deeper exploration:
MQTT Protocol: The pub/sub messaging pattern used in the state synchronization example is implemented via MQTT in production IoT systems. WebSocket Communication: Real-time bidirectional communication for push-based state updates mentioned in the Ecobee case study. Event-Driven Architecture: Notification escalation and geofence triggers are examples of event-driven IoT design. Accessibility Standards: WCAG 2.1 requirements for aria-live regions and screen reader compatibility in optimistic UI. Network Quality of Service: Understanding network latency variability helps design appropriate timeout and retry logic.
Hands-on exercises to reinforce interaction pattern concepts:
50.8.3 Optimistic UI for Smart Locks
Build a simple web interface that demonstrates optimistic UI with rollback:
- Create an HTML button labeled “Lock Door”
- On click, immediately show “Locked” state with loading spinner
- Simulate 2-second network delay with
setTimeout - 80% of the time, confirm lock success; 20% simulate failure
- On failure, revert to “Unlocked” and show toast message
What to observe: Notice how instant feedback feels more responsive than waiting 2 seconds. Test clicking the button multiple times rapidly—without optimistic UI, users queue multiple commands. With it, the button disables during pending state.
50.8.4 Exercise 2: Test Notification Fatigue
Configure a smart home simulator with motion sensors:
Set sensors to trigger notifications for every motion event. Simulate 50 motion events in 10 minutes (typical busy household). Track when you start ignoring notifications. Re-configure with escalation: silent log (trees), badge (routine), push (unusual), alarm (security).
What to observe: Notice the point where you mentally tune out notifications. Compare notification counts: 50 vs. 3-5 after escalation filtering.
50.8.5 Exercise 3: State Sync Race Condition
Create a multi-client simulation:
- Open the Wokwi ESP32 MQTT simulator in two browser tabs
- Both tabs subscribe to
home/thermostat/temperaturetopic - Both tabs publish temperature changes simultaneously
- Observe last-write-wins behavior
What to observe: Which client’s change “wins”? Notice how timestamp resolution affects conflict detection. See what happens if clocks are skewed between clients.
50.9 What’s Next
| Next Topic | Description |
|---|---|
| Multimodal Interaction | Voice, touch, gesture modalities with accessibility and graceful degradation |
| Process & Checklists | Iterative design methodology and validation checklists |
| Worked Examples | Voice interface design case study for elderly users |
| Hands-On Lab | Build an accessible IoT interface with ESP32 and OLED |
50.10 Continue Your Route
This final part closes the route from Ecobee State Synchronization through What’s Next. Return to Interaction Patterns: State and Feedback or continue from the ux-design module index.
