Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
The optimistic/pessimistic choice is not binary. In practice, most production UIs operate on a spectrum: fully pessimistic (block until confirmed), partially optimistic (update UI but show a pending indicator), and fully optimistic (update UI with no pending indicator, silent network call). Each point on the spectrum has clear appropriate uses. Fully optimistic is right for social actions where failure is rare and the rollback cost is low. Partially optimistic is right for content edits where you want to show the result immediately but signal that save is in progress. Fully pessimistic is right for irreversible or high-stakes operations. Recognizing where a given action falls on this spectrum, and being consistent within an application, is what separates polished UX from janky UX.
The same 'follow user' action implemented three ways: fully pessimistic (spinner, button disabled), partially optimistic (button updates, subtle pending dot), and fully optimistic (instant, silent).
0.5 and click each button five times — record how the user experience of failure differs across the three implementations.3000ms in PessimisticFollow and compare the perceived responsiveness — explain in one sentence why fully optimistic is clearly better at this latency.OptimisticFollow when the catch block fires — verify it is visible at the 10% default failure rate.data-testid attribute to each button and write a test assertion that verifies PessimisticFollow has disabled=true during the 800ms pending window.import { useState } from "react";
async function followUser(id: string): Promise<void> {
await new Promise((r) => setTimeout(r, 800));
if (Math.random() < 0.1) throw new Error("failed");
}
// Fully pessimistic
export function PessimisticFollow({ userId }: { userId: string }) {
const [following, setFollowing] = useState(false);
const [loading, setLoading] = useState(false);
async function handleFollow() {
setLoading(true);
try {
await followUser(userId);
setFollowing((v) => !v);
} finally {
setLoading(false);
}
}
return (
<button onClick={handleFollow} disabled={loading}>
{loading ? "..." : following ? "Following" : "Follow"}
</button>
);
}
// Partially optimistic
export function PartialFollow({ userId }: { userId: string }) {
const [following, setFollowing] = useState(false);
const [pending, setPending] = useState(false);
async function handleFollow() {
const prev = following;
setFollowing(!prev);
setPending(true);
try {
await followUser(userId);
} catch {
setFollowing(prev);
} finally {
setPending(false);
}
}
return (
<button onClick={handleFollow}>
{following ? "Following" : "Follow"}
{pending && <span style={{ marginLeft: 4, opacity: 0.5 }}>•</span>}
</button>
);
}
// Fully optimistic
export function OptimisticFollow({ userId }: { userId: string }) {
const [following, setFollowing] = useState(false);
async function handleFollow() {
const prev = following;
setFollowing(!prev);
try {
await followUser(userId);
} catch {
setFollowing(prev);
}
}
return (
<button onClick={handleFollow}>
{following ? "Following" : "Follow"}
</button>
);
}