Design Promise failure propagation
Explain then/catch/finally behavior across async boundaries. Then apply it to a realistic product screen where a user action, browser behavior, and rendering timing all matter.
Answer Strategy
For promise failure propagation, 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 promise failure propagation
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
Shows whether you understand promise failure propagation as an operating model, not as memorized trivia.
Constraints
- Use one concrete browser or React-facing example.
- Name the failure mode a production user would notice.
- Keep the first answer under two minutes before expanding.
Model Answer Shape
- Start with the rule: then/catch/finally behavior across async boundaries.
- Tie the rule to ownership: what runs in render, what runs after paint, what is external state, and what must be cleaned up.
- Close with the smallest test, trace, or code review check that would catch the bug.
Tradeoffs
- A short interview answer is easier to follow, but a senior answer must still name the edge case.
- Framework vocabulary helps only after the browser or language rule is clear.
Edge Cases
- Slow devices where timing bugs become visible.
- Repeated user actions before async work settles.
- Browser defaults that differ from custom component behavior.
Testing And Proof
- Unit-test the pure decision when possible.
- Use an interaction test for focus, keyboard, timing, or cleanup behavior.
Follow-Ups
- How would this change in a React component?
- What would you log or profile if this broke in production?