Lesson 1 of 30
Every fetch a UI makes actually has four states to design for, not two — and modeling them as a boolean instead of a real enum is where most loading-spinner bugs come from.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
A fetch resolves successfully and returns an empty array. What's the correct UI for this case?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A component that fetches data isn't just "loading" or "loaded." It moves through a real sequence of states, and the UI has to render something correct for each one:
The mistake that causes real bugs is representing this as a handful of independent booleans — isLoading, isError, hasData — instead of one variable that can only ever be exactly one of these states at a time.
type FetchState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T[] }
| { status: "error"; message: string };
function useSearchResults(query: string) {
const [state, setState] = useState<FetchState<Result>>({ status: "idle" });
useEffect(() => {
if (!query) {
setState({ status: "idle" });
return;
}
let cancelled = false;
setState({ status: "loading" });
fetchResults(query)
.then((data) => {
if (!cancelled) setState({ status: "success", data });
})
.catch((err) => {
if (!cancelled) setState({ status: "error", message: err.message });
});
return () => {
cancelled = true;
};
}, [query]);
return state;
}
Idle: no request has started. Nothing to render yet.
The rendering side then does an exhaustive switch on status, and this is where the empty state earns its own branch: state.status === "success" && state.data.length === 0 is not an error and it's not still loading — it's a correct, successful response that happens to have nothing in it. It needs its own UI (usually "no results, try a different search" style copy), because rendering nothing at all there looks broken, and rendering the loading spinner there is simply wrong.
isLoading and error can both be true at once, and nothing stops that. With independent booleans there's no code-level guarantee the UI can't ask "are we loading AND erroring AND have data" simultaneously — a string-union state makes that combination unrepresentable instead of just unlikely."Walk me through how you'd manage the states of a data-fetching component" is a real, common question, and naming idle/loading/success/error plus the empty-vs-populated split inside success — unprompted — is what separates a candidate who's shipped this from one who's only used isLoading on autopilot.