Detailed explanation
I debug a React app by first reproducing the issue reliably and classifying it: incorrect UI, unexpected state, failed requests, runtime errors, or slow rendering. I note the exact interaction, expected result, and actual result, then reduce the problem to the smallest relevant component or data flow. Browser console errors and their JavaScript stack traces are useful starting points. When React provides a component stack, it identifies the affected component hierarchy, helping locate the failing subtree rather than only the function where an exception occurred.
Next, I use React DevTools to inspect the component tree, current props, state, hooks, and context. I trace data from its owner down to the component displaying the wrong result. For stale UI, I check for direct state mutation, incorrect effect dependencies, stale closures, and unstable list keys. React treats state as a snapshot for each render, so logging state immediately after calling its setter can show the previous value. Browser breakpoints and targeted logs help verify which handler or effect runs and which values it captures.
For unnecessary renders or slow interactions, I record the relevant action with the React DevTools Profiler. I inspect commits, component render durations, and render reasons where available; recording why components render may require enabling a profiler setting first. A component can render because its state changes, its parent renders, or a consumed context changes. A render does not necessarily mean React changes the DOM. I investigate expensive work and unstable object or function props before adding memoization. Development Strict Mode can repeat renders and effect setup and cleanup checks, so extra logs alone do not prove a production bug.
For data problems, I inspect the browser Network panel for request payloads, status codes, response bodies, and request ordering. I also check loading and error handling, effect cleanup, and races where an older response overwrites newer data. After fixing the root cause, I repeat the reproduction and add an appropriate regression test. Interviewers look for this evidence-driven workflow, not just a list of debugging tools.
Key points to remember
- Reproduce and classify the issue before changing code.
- Inspect props, state, hooks, and context with React DevTools.
- Use JavaScript and component stacks to narrow down runtime failures.
- Profile a specific interaction to identify expensive renders and their causes.
- Check network behaviour, verify the fix, and add a regression test.
Common mistakes candidates make
- Adding React.memo, useMemo, or useCallback without measuring the actual bottleneck.
- Treating every render as a DOM update or interpreting Strict Mode checks as production behaviour.
- Changing effect dependencies or suppressing errors without tracing the underlying data flow.
Likely follow-up questions
How do you find why a component re-renders?
Enable render-reason recording in React DevTools where available, record the interaction in the Profiler, and inspect the component in the relevant commit. Check state updates, parent renders, context changes, and prop identity changes; a parent render can also render a child whose props have not changed.
How do you debug an effect that runs repeatedly?
Check whether the effect updates state that changes one of its dependencies, or whether an object or function dependency is recreated on every render. Distinguish that cycle from development Strict Mode checks, and fix the dependency or effect design rather than blindly removing dependencies.
Can an error boundary help with debugging?
An error boundary can show fallback UI and report errors with component-stack information for failures in descendant rendering and lifecycle work. It does not generally catch event-handler errors or ordinary asynchronous callback errors, so those need separate handling.
Interview tip: Describe one reproducible bug, explain which tool you would use and what evidence you would inspect, then state how you would verify the fix.
