Q379: Async UI Drill Search Filters Cancellation and Stale Responses
What Interviewers Want To Evaluate
This drill tests whether you can build asynchronous UI that behaves correctly under real user speed and real network conditions.
Search and filtering questions are common because they expose many frontend fundamentals at once.
The interviewer wants to see state ownership, cancellation, debouncing, stale response prevention, URL state, accessibility, loading states, and test strategy.
Short Interview Answer
For async search UI, I model query and filters separately from results, debounce noisy input when appropriate, cancel or sequence requests, and ensure only the latest request can update visible results. I also handle loading, empty, error, and partial states, keep shareable filters in the URL when needed, and test slow network and out-of-order responses.
Core Problem
Users can:
type quickly
change filters repeatedly
navigate back
refresh the page
lose connection
submit the same search twice
clear input
receive slow responses
The UI must not assume a perfect network.
It must be designed for overlap.
State Model
Separate:
draft query
committed query
filters
sort
pagination
request status
results
error
selected item
Draft query is what the user is typing.
Committed query is what the app is searching.
That distinction makes debouncing and submit-on-enter flows easier to reason about.
URL Ownership
Put state in the URL when it should survive:
refresh
sharing
back and forward navigation
deep links
saved views
Keep state local when it is temporary:
input focus
open dropdown
hovered row
unsaved draft
transient loading spinner
Search pages usually benefit from URL query params.
Small pickers inside forms may not.
Stale Response Bug
Problem:
request A searches "rea"
request B searches "react"
request B finishes first
request A finishes later
UI shows "rea" results while input says "react"
This is a classic async UI bug.
Senior engineers should name it quickly.
Prevention Pattern
Use cancellation:
useEffect(() => {
const controller = new AbortController();
async function runSearch() {
setState({ status: "loading" });
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
});
const results = await response.json();
setState({ status: "success", results });
} catch (error) {
if (controller.signal.aborted) return;
setState({ status: "error", message: "Search failed." });
}
}
runSearch();
return () => controller.abort();
}, [query]);
Aborting the old request prevents it from updating state.
Sequence Pattern
Sometimes cancellation is not enough or the API cannot be aborted.
Use a sequence guard:
const requestIdRef = useRef(0);
useEffect(() => {
const requestId = requestIdRef.current + 1;
requestIdRef.current = requestId;
async function runSearch() {
const results = await search(query);
if (requestId !== requestIdRef.current) {
return;
}
setResults(results);
}
runSearch();
}, [query]);
Only the latest request is allowed to commit.
Debounce Trade-Off
Debounce helps when:
typing fires many requests
API cost matters
results are not needed on every keystroke
network noise creates flicker
Avoid too much debounce when:
search must feel instant
local filtering is cheap
the user explicitly presses enter
accessibility feedback becomes delayed
Debounce is a product experience decision, not only a performance trick.
Loading UX
Possible loading states:
initial skeleton
inline spinner
dim old results while refreshing
optimistic filter chips
progress text for long operations
For search, keeping old results visible while showing refresh state can feel better than blanking the page.
But the UI must make it clear that results are updating.
Accessibility
Search UI should support:
labelled input
keyboard filter controls
focus order
screen reader result count
announced loading changes when meaningful
clear button with accessible label
semantic list or table
no focus loss after results update
Fast async updates should not make keyboard users lose their place.
Error and Empty States
Separate:
no query yet
no matching results
network failure
server error
unauthorized
invalid filter
These are not the same user message.
A good empty state helps the user adjust search terms.
A good error state helps them retry or recover.
Testing Drill
Test:
debounced request fires after delay
old request cannot overwrite new results
abort happens on query change
empty state appears
error state appears
URL params initialize search
back button restores previous filters
keyboard can use filters
The stale response test is the highest-signal test.
It proves production thinking.
Interview Answer Structure
Use:
requirements
state ownership
request lifecycle
stale response prevention
URL and navigation
loading empty error states
accessibility
testing
trade-offs
This turns a simple search prompt into a senior answer.
Common Mistakes
- Firing a request on every keystroke without considering cost.
- Not preventing stale responses.
- Clearing results during every refresh and causing flicker.
- Hiding URL state that should be shareable.
- Forgetting empty and error states.
- Losing focus after results update.
- Testing only the happy path.
Final Mental Model
Async search UI is:
state ownership
request lifecycle
cancellation
latest result wins
clear recovery
accessible feedback
The hard part is not fetching data.
The hard part is keeping the UI truthful while the world changes underneath it.