Debug transition-driven filtering under interview pressure
A React screen using transition-driven filtering passes simple tests but breaks during repeated interaction. Find the likely root cause, patch it, and describe the longer-term design improvement.
Answer Strategy
For transition-driven filtering under interview pressure, do not start with a rewrite. Start with a failing sequence: what the user did, what they saw, what state should have owned the result, and which boundary allowed stale or invalid data through.
Debugging interviews reward evidence. State a hypothesis, inspect the smallest owner boundary, patch that boundary, and leave behind a regression test that would have failed before the fix.
The reference implementation fixes the most common frontend production bug: earlier async work overwriting later user intent after a route change, filter change, or unmount.
Reference Implementation: Regression Fix For transition-driven filtering under interview pressure
This pattern applies to search, filters, forms, polling, and UI components where the latest user intent must win over older async work.
type SearchUser = { id: string; name: string };
type SearchState =
| { tag: 'idle'; users: SearchUser[] }
| { tag: 'loading'; query: string; users: SearchUser[] }
| { tag: 'success'; query: string; users: SearchUser[] }
| { tag: 'error'; query: string; message: string; users: SearchUser[] };
function useUserSearch(query: string) {
const [state, setState] = React.useState<SearchState>({
tag: 'idle',
users: [],
});
React.useEffect(() => {
const normalized = query.trim();
if (!normalized) {
setState({ tag: 'idle', users: [] });
return;
}
const controller = new AbortController();
setState((previous) => ({
tag: 'loading',
query: normalized,
users: previous.users,
}));
fetch('/api/users?q=' + encodeURIComponent(normalized), {
signal: controller.signal,
})
.then((response) => {
if (!response.ok) throw new Error('HTTP ' + response.status);
return response.json() as Promise<{ users: SearchUser[] }>;
})
.then((payload) => {
if (!controller.signal.aborted) {
setState({ tag: 'success', query: normalized, users: payload.users });
}
})
.catch((error) => {
if (controller.signal.aborted) return;
setState((previous) => ({
tag: 'error',
query: normalized,
message: error instanceof Error ? error.message : 'Unknown error',
users: previous.users,
}));
});
return () => controller.abort();
}, [query]);
return state;
}Testing Strategy
Convert the answer into observable behavior. In a mid-senior interview, say which behaviors are covered by unit tests, interaction tests, accessibility checks, and one browser smoke path.
test('aborts stale search requests when the query changes', async () => {
const aborts: string[] = [];
vi.stubGlobal('fetch', vi.fn((url: string, init?: RequestInit) => {
init?.signal?.addEventListener('abort', () => aborts.push(url));
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ users: [{ id: 'u1', name: 'Ada' }] }),
} as Response);
}));
const { rerender } = renderHook(({ query }) => useUserSearch(query), {
initialProps: { query: 'react' },
});
rerender({ query: 'vue' });
expect(aborts.some((url) => url.includes('react'))).toBe(true);
});Interviewer Signal
Tests whether you debug from ownership and lifecycle instead of random dependency-array edits.
Constraints
- State a hypothesis before changing code.
- Name what evidence would confirm the bug.
- Avoid broad rewrites unless the current API cannot express the behavior.
Model Answer Shape
- Reproduce the failing sequence first.
- Inspect ownership boundaries: local state, props, effects, subscriptions, and server data.
- Patch the minimal broken boundary and add a regression test.
Tradeoffs
- A minimal patch reduces risk, but repeated lifecycle bugs often justify a small reducer or custom hook.
- Adding dependencies can silence lint warnings while still preserving the wrong ownership model.
Edge Cases
- Double clicks and repeated submissions.
- Slow network responses arriving out of order.
- Component remount with stale persisted state.
Testing And Proof
- Failing interaction sequence.
- Out-of-order async response.
- Unmount cleanup.
Follow-Ups
- What would the code review comment say?
- What metric or log would show this in production?