← All courses

Interview questionsWeb DevelopmentIntermediate

Top 10 React interview questions

The React questions asked most in fresher and junior frontend interviews, from the virtual DOM and keys to hooks, effects and performance, with model answers and code.

10 min · 10 questions

1What is the virtual DOM, and how does React update the screen?

The virtual DOM is a lightweight JavaScript description of the UI: plain objects that say "a div with this class containing this text". When state changes, React:

  1. Calls your components again to produce a new virtual tree.
  2. Diffs it against the previous tree (reconciliation).
  3. Commits only the differences to the real DOM.

Why it helps: real DOM operations (layout, paint) are expensive; comparing JS objects is cheap. It also lets you write UI declaratively ("for this state, show this") instead of manually adding and removing nodes.

React's diff uses two assumptions to stay fast (O(n) instead of O(n³)): elements of a different type produce different trees (a div turning into a section is rebuilt), and key props identify which list items are the same across renders.

Strong answer bonus: mention that "virtual DOM is faster than the DOM" is a simplification. Its real value is a simple programming model with good-enough performance by default, and that React Fiber lets React split rendering work into chunks and prioritise urgent updates like typing.

2Why do lists need keys, and why is using the index a problem?

Keys tell React which item is which between renders. With keys, React can match old and new items and move, add or remove only what changed.

If you use the array index and the list is reordered, filtered, or an item is inserted at the top, every index shifts. React then reuses the component at index 0 for what is now a different item. Visible bugs: an input keeps the text of a different row, a checkbox stays ticked on the wrong item, animations jump.

jsx
// Good
{todos.map((t) => <TodoItem key={t.id} todo={t} />)}

// Risky if the list can change order
{todos.map((t, i) => <TodoItem key={i} todo={t} />)}

Keys must be stable, unique among siblings and not random (Math.random() creates a new key every render and remounts every item). The index is fine only for static lists.

3What is the difference between state and props?

PropsState
Owned bythe parentthe component itself
Changeable by the component?no, read-onlyyes, via the setter
Purposeconfigure a component from outsideremember data that changes over time
Change causes re-render?yes, when the parent passes new propsyes

Data flows down through props. To let a child change parent data, the parent passes a callback (onChange) as a prop. If two components need the same state, lift it to their closest common parent.

4Explain useEffect, its dependency array and cleanup

useEffect runs code after render to synchronise a component with something outside React: fetching data, subscriptions, timers, manual DOM work.

  • No array: runs after every render.
  • []: runs once after mount; cleanup runs on unmount.
  • [a, b]: runs after mount and whenever a or b changes.

The function you return is the cleanup. It runs before the effect re-runs and when the component unmounts.

jsx
useEffect(() => {
  const id = setInterval(() => setSeconds((s) => s + 1), 1000);
  return () => clearInterval(id); // no leaked timers
}, []);

Common mistakes to mention: missing dependencies (stale values), objects or functions in the dependency array that change every render (infinite loops), and using effects to compute derived values that could simply be calculated during render.

5Why doesn't state update immediately after calling setState?

Calling a setter schedules a re-render; it doesn't change the variable in the current render. Each render "sees" a snapshot of state.

jsx
const [count, setCount] = useState(0);
function add3() {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1); // count is still 0 in this render, so the result is 1
}
function add3Correctly() {
  setCount((c) => c + 1);
  setCount((c) => c + 1);
  setCount((c) => c + 1); // updater functions chain: result is 3
}

React also batches multiple updates in one event (and, since React 18, in promises and timeouts) into a single re-render for performance. Use the updater form whenever the next state depends on the previous one.

6What are controlled and uncontrolled components?

  • Controlled: React state holds the input's value; the input displays value={state} and updates it with onChange. You can validate, format or disable buttons on every keystroke.
  • Uncontrolled: the DOM holds the value; you read it when needed with a ref or FormData on submit. Less code and fewer re-renders.
jsx
// Controlled
<input value={email} onChange={(e) => setEmail(e.target.value)} />

// Uncontrolled
const ref = useRef();
<input ref={ref} defaultValue="" />
// on submit: ref.current.value

Choose controlled for live validation and dependent fields; uncontrolled (or React Hook Form) for large forms read only on submit.

7What are useMemo, useCallback and React.memo, and when should you use them?

  • React.memo(Component) skips re-rendering a component when its props are shallowly equal.
  • useMemo(() => value, deps) caches a computed value between renders.
  • useCallback(fn, deps) caches a function reference between renders.

They work together: if a memoized child receives a new function or object every render, the memo never helps, so you stabilise those props with useCallback/useMemo.

Use them when you have measured a problem: an expensive calculation (sorting or filtering thousands of items), or a heavy child that re-renders needlessly. Don't wrap everything; memoization has its own cost and makes code harder to read. Often moving state down or passing children is a simpler fix.

8What is prop drilling, and how do you avoid it?

Prop drilling is passing a prop through several components that don't use it, just to reach a deeply nested child. It makes components harder to change and reuse.

Ways to avoid it:

  1. Composition: pass components as children or props so the parent renders the deep child directly.
  2. Context: createContext + useContext for values many components need (theme, user, locale).
  3. A state library (Zustand, Redux Toolkit) for complex global state that changes often.

Mention the trade-off: every consumer of a context re-renders when its value changes, so split contexts by concern or use a store with selectors for fast-changing data.

9What are custom hooks? Write one.

A custom hook is a function whose name starts with use and that calls other hooks. It lets you reuse stateful logic across components; each component that calls it gets its own independent state.

jsx
function useFetch(url) {
  const [state, setState] = useState({ data: null, error: null, loading: true });
  useEffect(() => {
    const ctrl = new AbortController();
    setState({ data: null, error: null, loading: true });
    fetch(url, { signal: ctrl.signal })
      .then((r) => (r.ok ? r.json() : Promise.reject(new Error(r.status))))
      .then((data) => setState({ data, error: null, loading: false }))
      .catch((error) => error.name !== "AbortError" && setState({ data: null, error, loading: false }));
    return () => ctrl.abort();
  }, [url]);
  return state;
}

const { data, loading, error } = useFetch(`/api/jobs?q=${query}`);

Other good examples: useDebounce, useLocalStorage, useOnClickOutside, usePrevious.

10How would you improve the performance of a slow React app?

Answer as a process, not a list of tricks:

  1. Measure: React DevTools Profiler to see what renders and how long it takes; Lighthouse and the network tab for load time.
  2. Fix rendering: move state closer to where it's used, avoid needless re-renders with React.memo + stable props, memoize expensive calculations, split large contexts.
  3. Fix large lists: pagination, infinite scroll or virtualization (react-window).
  4. Fix loading: code splitting with React.lazy/route-based chunks, smaller dependencies, optimised images, caching API data, server rendering for first paint.
  5. Keep input snappy: debounce search requests; useTransition for heavy updates.
  6. Measure again to prove it helped.

Interviewers want to hear that you wouldn't optimise blindly.

Keep going