← Back to question bank
DebuggingSeniorHard#3024 · 35m

Review a broken drag and drop reorder list

Given a drag and drop reorder list that looks correct but fails under keyboard use, slow data, or repeated interaction, identify the production risks and propose fixes.

Answer Strategy

For drag and drop reorder list, 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 drag and drop reorder list

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

Tests senior review instincts: behavior, state ownership, accessibility, and tests.

Constraints

  • Prioritize user-impacting bugs before visual polish.
  • Name the smallest safe patch and the follow-up refactor.
  • Tie each finding to a test or story state.

Model Answer Shape

  • Scan focus management, state synchronization, async cleanup, and labels first.
  • Separate immediate fixes from API redesign.
  • Leave behind a regression test for each fixed behavior.

Tradeoffs

  • Fixing locally is faster, but component API bugs should move into the shared primitive.
  • Strict accessibility can reveal product-copy ambiguity that engineering cannot solve alone.

Edge Cases

  • Component unmounts during async work.
  • Selection changes while filtered data updates.
  • Nested interactive elements with conflicting keyboard handlers.

Testing And Proof

  • Interaction test for the regression.
  • Screen-reader-facing labels and focus assertions.
  • Story for the broken state.

Follow-Ups

  • What would you ask design or product before changing the behavior?
  • How would you roll this fix through a component library?