Lesson 21 of 30
A search box is a deceptively small surface with a lot of UX riding on it — how long the results feel like they're keeping up, what fills the box before anyone types, and what happens the moment a search comes back empty.
11 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 search box shows nothing at all — just an empty input and a blinking cursor — until the user types their first character. What UX opportunity is this missing?
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 search box looks like the simplest possible UI element, but four separate decisions determine whether it feels genuinely helpful or just technically functional.
How long to wait before firing a request is a perceptual decision, not just a performance one. The JavaScript track's debounce lesson covers the mechanism (delay a function call until input goes quiet); this is about the feel of the delay itself. Too short (50ms) fires a request on nearly every keystroke, producing flickering results and unnecessary network noise. Too long (600–800ms) feels laggy, like the box isn't responding to what was just typed. Something in the 250–300ms range is a common sweet spot: long enough that a fast typer finishes a word before anything fires, short enough that results still feel connected to the keystroke that triggered them.
What fills the box before anyone types anything is real, valuable real estate, not a blank space to ignore. Showing the user's own recent searches, or aggregate popular ones, gives a useful default state instead of an empty input and a blinking cursor — and often lets a returning user re-run a past search in one tap instead of retyping it.
const debouncedQuery = useDebouncedValue(query, 280); // fast enough to feel live, slow enough not to fire per keystroke
useEffect(() => {
if (!debouncedQuery) return;
fetchResults(debouncedQuery);
}, [debouncedQuery]);
Once results come back, highlighting the matched substring inside each result (bolding the "data" inside "database") lets a user visually confirm why each result matched instead of reading every result in full to figure out relevance — a small change that measurably speeds up scanning a results list.
// safe: escape the query before building the highlight regex, never inject raw
// user input into innerHTML directly
function highlightMatch(text: string, query: string) {
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return text.replace(new RegExp(`(${escaped})`, "gi"), "<mark>$1</mark>");
}
And when nothing matches at all, a genuinely helpful zero-results state does more than report failure. "No results found" is a dead end. Suggesting a likely correction ("Did you mean 'database'?"), broadening to a related category, or offering another path forward turns a failed search into a recoverable moment instead of a wall.
"The search feels laggy" is a common bug report, and the strong answer goes past "the API is slow" to consider whether the debounce delay itself is tuned wrong, whether the empty and zero-results states are pulling their weight, and whether matched-text highlighting is even implemented safely — search UX is a small surface with disproportionately many places to get wrong.