← Back to question bank
DebuggingMidMedium#1023 · 30m

Design Generic API modeling

Explain generic constraints, inference, overloads, and utility types. Then apply it to a realistic product screen where a user action, browser behavior, and rendering timing all matter.

Answer Strategy

For generic API modeling, 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 generic API modeling

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.

Reproduce
Write the exact failing path as an interaction test before changing the implementation.
Patch
Fix ownership at the smallest boundary: abort old work, ignore stale results, or move ambiguous state into a reducer.
Prevent
Add a regression test and a production signal such as client errors, abandoned workflow, or retry rate.
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 generic api modeling 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: generic constraints, inference, overloads, and utility types.
  • 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?