← Back to question bank
DebuggingSeniorHard#6009 · 45m

Practice: write visual regression states

Turn "write visual regression states" into a concrete interview exercise. Explain the risk, choose the smallest useful test boundary, and describe how the signal prevents regressions.

Answer Strategy

For write visual regression states, 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 write visual regression states

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 can prove frontend behavior instead of relying on screenshots or manual confidence.

Constraints

  • Choose unit, integration, E2E, visual, or performance testing deliberately.
  • State the failure that the test catches.
  • Avoid brittle assertions that lock implementation details.

Model Answer Shape

  • Start with the user-impacting behavior.
  • Pick the smallest test that sees that behavior.
  • Add one higher-level test only when timing, browser behavior, or integration risk requires it.

Tradeoffs

  • Unit tests are fast and precise but cannot prove browser wiring.
  • E2E tests are realistic but should be reserved for workflows where integration risk matters.

Edge Cases

  • Out-of-order async results.
  • Environment-specific browser behavior.
  • False confidence from mocks that do not match production contracts.

Testing And Proof

  • Regression case for the named risk.
  • Negative path or error state.
  • Cleanup or retry behavior when relevant.

Follow-Ups

  • What would make this test flaky?
  • What would you monitor after shipping the fix?