Quick Answer

React interviews concentrate on the component lifecycle and rendering model, useState and useEffect including dependency arrays, why keys matter in lists, controlled versus uncontrolled components, props versus state, and the virtual DOM. The questions that separate candidates are why a state value looks stale immediately after setting it, and what an incorrect useEffect dependency array actually breaks.

Fundamentals

What is the virtual DOM?

An in-memory representation of the UI. On a state change React builds a new tree, compares it with the previous one (reconciliation), and applies only the differences to the real DOM. Direct DOM manipulation is slow, so batching the minimum set of changes is the win.

A good addition: the virtual DOM is not automatically faster than hand-written DOM code — it is faster than naive re-rendering, and it buys you a declarative model.

What is JSX?

Syntax that looks like HTML and compiles to React.createElement calls. It is not a template language and it is not HTML — which is why attributes are className and htmlFor, and why you can embed any JavaScript expression in braces.

Props vs state?

Props are passed in by the parent and are read-only within the component. State is owned and updated by the component itself. The one-line version interviewers want: props are arguments, state is memory.

What is a controlled component?

A form input whose value comes from React state, with an onChange handler updating it. Uncontrolled inputs keep their value in the DOM and are read with a ref. Controlled is the default recommendation because the value is always available for validation.

State, and the Question That Catches People

What does this log?

const [count, setCount] = useState(0);

function handleClick() {
  setCount(count + 1);
  console.log(count);      // logs 0, not 1
}

State updates are asynchronous and batched. count in this render is a constant — calling the setter schedules a re-render, it does not mutate the existing variable. The new value is only visible in the next render.

Follow-up: what does this produce?

setCount(count + 1);
setCount(count + 1);   // increments by 1 in total, not 2

Both calls read the same stale count. The fix is the functional form, which receives the latest value:

setCount(c => c + 1);
setCount(c => c + 1);   // now increments by 2

Use the updater function whenever the new state depends on the old one. This question appears constantly and answering it well signals real experience.

Why must state be updated immutably?

React compares by reference to decide whether to re-render. Mutating an object or array keeps the same reference, so React sees no change and skips the update.

items.push(x); setItems(items);      // no re-render — same array
setItems([...items, x]);            // new reference — re-renders

Hooks and Their Rules

What are the rules of hooks?

Call them only at the top level — never inside conditions, loops or nested functions — and only from React function components or custom hooks. The reason matters: React tracks hooks by call order, so a conditional hook shifts every subsequent one and state gets attached to the wrong variable.

Explain the useEffect dependency array.

useEffect(() => { ... });            // after every render
useEffect(() => { ... }, []);        // once, after the first render
useEffect(() => { ... }, [userId]);  // when userId changes

Be ready for the two failure modes. An empty array when the effect uses a prop causes a stale closure — the effect keeps the first value forever. Omitting the array entirely while setting state inside causes an infinite loop.

What is the cleanup function?

The function returned from an effect. React runs it before the next effect and on unmount — for cancelling subscriptions, clearing intervals and aborting fetches. Without it you get memory leaks and "cannot update state on an unmounted component".

useEffect(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id);
}, []);

useMemo vs useCallback?

useMemo caches a computed value; useCallback caches a function reference. Both exist to avoid unnecessary work or re-renders. Say that they are optimisations to apply when you have measured a problem, not by default — premature memoisation adds complexity and its own cost.

Keys, Lists and Rendering

Why does React need a key prop?

Keys let React match elements between renders. Without a stable identity, React cannot tell whether an item was added, removed or reordered, so it may reuse the wrong DOM node and the wrong component state.

Why is the array index a bad key?

Because it describes position, not identity. Delete the first item and every remaining item's index shifts, so React thinks each one changed. The visible symptom is input values and checkbox states attaching to the wrong rows after a delete or reorder.

{items.map((item, i) => <Row key={i} />)}        // breaks on reorder
{items.map(item => <Row key={item.id} />)}       // stable identity

Index keys are acceptable only for a static list that is never reordered, filtered or edited.

What causes unnecessary re-renders?

A parent re-rendering re-renders its children by default. Passing a new object or arrow function as a prop creates a fresh reference each render, defeating React.memo. Mention that the fix is useCallback or useMemo for those props — after confirming the re-render is actually a problem.

What is prop drilling and how do you avoid it?

Passing props through components that do not use them just to reach a deep child. Solved with Context for genuinely global values like theme or auth, or a state library for larger applications. Note that Context re-renders all consumers when its value changes, so it is not a general performance tool.

Practical and Lifecycle Questions

How do you fetch data in React?

In an effect, with cleanup to avoid setting state after unmount:

useEffect(() => {
  const controller = new AbortController();
  fetch(url, { signal: controller.signal })
    .then(r => r.json())
    .then(setData)
    .catch(e => { if (e.name !== 'AbortError') setError(e); });
  return () => controller.abort();
}, [url]);

Mention that production apps usually use a data library for caching and retries rather than hand-rolling this.

What replaced the class lifecycle methods?

componentDidMount becomes an effect with an empty dependency array. componentDidUpdate becomes an effect with dependencies. componentWillUnmount becomes the cleanup function.

What is lifting state up?

Moving state to the closest common ancestor when two siblings need it, then passing it down as props with a setter. The standard answer to "how do two components share state".

What is a custom hook?

A function starting with use that calls other hooks, letting you reuse stateful logic across components. Stress that it shares logic, not state — each component calling it gets its own independent state.

Why does my effect run twice in development?

React Strict Mode intentionally mounts, unmounts and remounts components in development to surface missing cleanup. It does not happen in production, and the correct response is to add cleanup rather than to disable Strict Mode.

Frequently Asked Questions

Why does my state look one step behind? State updates are asynchronous and batched, so the variable in the current render never changes. Reading it immediately after calling the setter shows the old value. Use the functional updater form when the new value depends on the previous one.
Why is using the array index as a key bad? Index describes position rather than identity, so removing or reordering items makes React associate the wrong DOM nodes and component state with the wrong data. Use a stable unique id from your data instead.
What happens if the useEffect dependency array is wrong? Missing a dependency causes a stale closure, where the effect keeps values from an earlier render forever. Omitting the array entirely while setting state inside causes an infinite render loop.
Should I use useMemo and useCallback everywhere? No. They add complexity and have their own cost, and most components are fast enough without them. Apply them where profiling shows a real problem, or where a memoised child depends on a stable reference.
Do I need to know class components for interviews? Enough to read them, since large codebases still contain them, and to map the old lifecycle methods onto hooks. New code is written with hooks, and interviews focus there.