Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Every network round-trip adds latency between a user's action and visible feedback. Research from Google and Nielsen Norman Group consistently shows that 100ms feels instantaneous, 300ms feels slow, and anything over 1 second breaks the user's flow. Modern mobile networks average 200–600ms per request, meaning a simple API call almost always lands in the 'feels slow' zone. Optimistic UI solves this by decoupling the visual response from the network response: the UI updates at zero latency, and the network call happens silently in the background. The user never waits — they only notice something happened if the mutation fails.
Compare two buttons side-by-side: one waits for a 400ms fake API call before updating its label, the other updates instantly. Use performance.now() to measure the gap between click and visible state change for each approach.
PessimisticButton from 400ms to 100ms and record whether the click still feels slow — note at which threshold your perception changes.performance.now() call inside OptimisticButton both before and after the setLiked call to verify the state update itself costs less than 5ms.fetch replacing the setTimeout.useRef) to measure how many times each button causes a re-render during a click cycle.Use these three in order. Each builds on the one before.
In one paragraph, explain what optimistic UI means and why it makes apps feel faster to users who are new to the concept.
Walk me through exactly how a browser perceives and reports latency — from mouse click to pixel paint — and where optimistic UI intervenes in that pipeline.
Given a mobile app where 30% of API calls fail due to poor connectivity, how would you adjust your optimistic UI strategy to avoid frustrating users with frequent rollbacks?
import { useState } from "react";
function PessimisticButton() {
const [liked, setLiked] = useState(false);
const [elapsed, setElapsed] = useState<number | null>(null);
async function handleClick() {
const start = performance.now();
// simulated 400ms network call
await new Promise((r) => setTimeout(r, 400));
setLiked((v) => !v);
setElapsed(Math.round(performance.now() - start));
}
return (
<div>
<button onClick={handleClick}>{liked ? "❤️ Liked" : "🤍 Like"}</button>
{elapsed !== null && <p>Perceived latency: {elapsed}ms</p>}
</div>
);
}
function OptimisticButton() {
const [liked, setLiked] = useState(false);
const [elapsed, setElapsed] = useState<number | null>(null);
async function handleClick() {
const start = performance.now();
setLiked((v) => !v); // instant update
setElapsed(Math.round(performance.now() - start));
await new Promise((r) => setTimeout(r, 400)); // real network call here
}
return (
<div>
<button onClick={handleClick}>{liked ? "❤️ Liked" : "🤍 Like"}</button>
{elapsed !== null && <p>Perceived latency: {elapsed}ms</p>}
</div>
);
}