The same React interview mistakes keep appearing. Senior developers with 5+ years of experience trip on the same questions that junior developers struggle with—not because React is hard to use, but because React is easy to use without understanding how it works.
These five mistakes cost developers job offers. Understanding what goes wrong and why will help you demonstrate the deep React knowledge interviewers are looking for.
This is the most common interview failure point. A candidate explains they've used React for years, then can't explain why their effect runs infinitely or never updates.
When asked "What happens when userId changes?", weak answers include:
"It fetches the new user" (wrong—the effect never re-runs)
"I disabled the lint warning so it's fine" (red flag)
"I'm not sure why the linter complains" (concerning uncertainty)
How should you answer the useEffect dependency question?
"This effect has a stale closure problem. The empty dependency array means it only runs on mount, but it uses userId from props. When userId changes, the effect doesn't re-run—we're stuck showing the old user.
The fix is to include userId in the dependency array:
The lint rule exists because React can't detect what values you use inside the effect. Every value from component scope used inside useEffect must be in the dependency array, or you'll have stale data."
What Interviewers Want to Hear
You understand closures - Effects capture values from the render they were created in
You respect the lint rules - Disabling exhaustive-deps is almost always wrong
You can identify stale closures - Recognizing when values won't update
How do you avoid unnecessary effect re-runs?
If they ask "How do you avoid unnecessary effect re-runs?", be ready:
"Three strategies:
Move functions inside the effect - If a function is only used in the effect, define it there so it's not a dependency
Use useCallback for functions - If the function needs to be outside, wrap it in useCallback with its own dependencies
Use refs for values you don't want to trigger re-runs - useRef gives you a mutable container that doesn't cause re-renders
But the first question should be: does this even need to be an effect? Data fetching often belongs in event handlers or a data fetching library like React Query."
Re-render Questions
Every React interview probes rendering behavior. The question that catches people: "Why is this component slow?"
What is the common re-render mistake?
function App() { const [count, setCount] = useState(0); return ( <div> <button onClick={() => setCount(c => c + 1)}> Clicked {count} times </button> <ExpensiveComponent /> {/* Re-renders on every click! */} </div> );}function ExpensiveComponent() { // Heavy computation on every render const result = heavyCalculation(); return <div>{result}</div>;}
Weak answers:
"Wrap everything in useMemo" (cargo culting)
"Use React.memo on every component" (misses the point)
"I'd need to profile it" (avoiding the conceptual question)
How should you answer the re-render question?
"When App's state changes, React re-renders App and all its children by default—including ExpensiveComponent, even though it receives no props. This is React's reconciliation model.
The fix depends on the situation:
If ExpensiveComponent doesn't need App's state:
const MemoizedExpensive = React.memo(ExpensiveComponent);// Now it only re-renders if its props change (none, so never)
If the computation is the expensive part:
function ExpensiveComponent() { const result = useMemo(() => heavyCalculation(), []); return <div>{result}</div>;}
Best approach—restructure to avoid the problem:
function App() { return ( <div> <Counter /> {/* State lives here now */} <ExpensiveComponent /> {/* Never re-renders */} </div> );}
Moving state down or lifting content up is often better than memoization."
What Interviewers Want to Hear
You understand the default behavior - State change = re-render self + children
You know multiple solutions - memo, useMemo, restructuring
You prefer structural fixes - Memoization is a last resort, not first instinct
When should you NOT use React.memo?
If they ask "When should you NOT use React.memo?", be ready:
"Don't use React.memo when:
Props change frequently - Memo adds comparison overhead for no benefit
The component is cheap to render - The memo comparison might cost more than just rendering
Props include children or render props - These create new references every render, defeating memo
Profile first. React DevTools Profiler shows exactly what's re-rendering and why. Premature optimization with memo can actually make things slower."
State Update Questions
This catches even experienced developers. They understand React is declarative but forget state updates are asynchronous.
When asked "What's the count after clicking?", weak answers include:
"3" (wrong—it's 1)
"I'm not sure, I'd have to run it" (should know conceptually)
"React batches updates so maybe 1?" (right answer, uncertain delivery)
How should you answer the state batching question?
"The count will be 1, not 3. Each setCount(count + 1) reads the same stale count value (0) because state updates are asynchronous and batched. All three calls effectively do setCount(0 + 1).
With functional updates, React queues each update and passes the latest pending state to the next updater function. The console.log still shows 0 though—you can't read the new state until the next render."
What Interviewers Want to Hear
You understand batching - React groups state updates for performance
You know the functional form - setState(prev => ...) for updates based on previous state
You know when to use which - Direct value for replacements, function for derivations
How is this different from class component setState?
If they ask "How is this different from class component setState?", be ready:
"Two key differences:
useState doesn't merge - this.setState({ name }) merged with existing state. setUser({ name }) replaces the entire state. For objects, spread manually: setUser(prev => ({ ...prev, name })).
No callback - Class this.setState({}, callback) ran after state updated. With hooks, use useEffect to react to state changes.
React 18 also made batching automatic everywhere—even in setTimeout and promises—whereas class components only batched in event handlers."
Rules of Hooks Questions
This is a fundamental React concept that trips up developers who learned hooks by example rather than understanding the rules.
What is the conditional hooks mistake?
function UserProfile({ userId }) { if (!userId) { return <div>Please select a user</div>; } // Error: Hook called conditionally! const [user, setUser] = useState(null); useEffect(() => { fetchUser(userId).then(setUser); }, [userId]); return <div>{user?.name}</div>;}
When asked "What's wrong with this code?", weak answers include:
"It looks fine to me" (misses the fundamental issue)
"Maybe the effect should have different deps" (wrong focus)
"I've done this before and it worked" (anecdotal, dangerous)
How should you answer the conditional hooks question?
"This breaks the Rules of Hooks. The early return means useState and useEffect aren't called when userId is falsy, but they are called when it's truthy. React tracks hooks by their call order—if the order changes between renders, React loses track of which hook is which.
The fix is to always call hooks unconditionally:
function UserProfile({ userId }) { const [user, setUser] = useState(null); useEffect(() => { if (!userId) return; fetchUser(userId).then(setUser); }, [userId]); if (!userId) { return <div>Please select a user</div>; } return <div>{user?.name}</div>;}
Hooks go at the top, early returns come after. The conditional logic moves inside the effect."
What Interviewers Want to Hear
You know the rules - Only call hooks at the top level, only call hooks from React functions
You understand why - React uses call order, not names, to track hooks
You use the linter - eslint-plugin-react-hooks catches these automatically
Can you call hooks in a loop?
If they ask "Can you call hooks in a loop?", be ready:
"No, for the same reason—the number of hook calls must be constant between renders. This breaks:
// Wrong: different number of hooks per renderitems.forEach(item => { const [selected, setSelected] = useState(false);});
Instead, lift state up or use a single state object:
If you need per-item state, consider a separate component for each item that manages its own state."
Key Prop Questions
This seems basic but catches developers constantly. It's often the source of mysterious bugs that are hard to trace.
What is the index key mistake?
function TodoList({ todos, onToggle }) { return ( <ul> {todos.map((todo, index) => ( <li key={index}> {/* Bug: using index as key */} <input type="checkbox" checked={todo.completed} onChange={() => onToggle(todo.id)} /> {todo.text} </li> ))} </ul> );}
When asked "What could go wrong here?", weak answers include:
"Nothing, I always use index as key" (wrong)
"The linter doesn't complain" (it should, or you're not using one)
"It's fine as long as the list doesn't change" (partial understanding)
How should you answer the key prop question?
"Using array index as key causes bugs when items are added, removed, or reordered. React uses keys to identify which items changed. With index keys, if I add an item at the beginning, every item's index shifts—React thinks every item changed and re-renders them all, potentially losing input state.
Real-world bug: with index keys, if each todo has an input field and I delete the second item, the third item's input shows the deleted item's text because React kept the DOM element and just updated the data."
What Interviewers Want to Hear
You understand reconciliation - Keys help React identify which elements changed
You know the failure modes - Wrong updates, lost state, performance issues
You use stable identifiers - Database IDs, UUIDs, anything that doesn't change
When is index as key actually okay?
If they ask "When is index as key actually okay?", be ready:
"Index keys are acceptable when ALL of these are true:
The list is static—no adds, removes, or reorders
Items have no state or uncontrolled inputs
Items have no unique IDs available
For static navigation menus or display-only lists that never change, index is fine. But the moment there's any interactivity or dynamic content, you need real keys.
Also never use key={Math.random()}—this creates a new key every render, forcing React to destroy and recreate the element, losing all state and killing performance."
Interview Strategy Questions
After conducting hundreds of React interviews, patterns emerge in what separates successful candidates:
Why should you explain the underlying model?
Good candidates don't just know the fix—they explain the underlying model. "useEffect needs dependencies because of closures" is better than "the linter says so."
Why should you know multiple solutions?
For re-render problems: memo, useMemo, restructuring. For state issues: functional updates, useReducer. Interviewers want to see you evaluate trade-offs, not reach for one tool every time.
When should you admit uncertainty?
"I'd profile this before adding memoization" shows mature judgment. "I always wrap everything in useMemo" shows cargo culting.
How does real debugging experience help?
"I've seen this cause bugs when..." is more convincing than "I read that this is bad." Interviewers can tell who's actually debugged these issues.