React useEffect Common Mistakes That Cost You Technical Interviews

React useEffect Common Mistakes That Cost You Technical Interviews

I've reviewed hundreds of React interviews, and I can tell you this: useEffect is where most candidates stumble. It's not enough to know the syntax—interviewers want to see that you understand the subtleties and can avoid the pitfalls that plague production React applications.

The useEffect hook seems straightforward on the surface, but it's packed with nuances that can make or break your interview performance. Let's dive into the specific mistakes I see repeatedly and how to avoid them.

Missing Dependency Arrays: The Silent Performance Killer

The most common mistake I encounter is omitting the dependency array entirely. Here's what this looks like:

// ❌ Wrong - runs on every render
useEffect(() => {
  fetchUserData(userId);
});

// ✅ Correct - runs only when userId changes
useEffect(() => {
fetchUserData(userId);
}, [userId]);

Without the dependency array, your effect runs after every render. In an interview setting, this immediately signals to the interviewer that you don't understand React's rendering cycle. Worse, it shows you might ship code that hammers APIs unnecessarily.

The fix is simple: always include the dependency array as the second argument. Include every value from component scope that's used inside the effect. Modern linters will catch this, but in a whiteboard interview, you won't have that safety net.

Forgetting Cleanup Functions: Memory Leaks in Production

This mistake is particularly damaging because it shows you haven't thought about the component lifecycle beyond the happy path. Event listeners, timers, and subscriptions need cleanup:

// ❌ Wrong - creates memory leaks
useEffect(() => {
  const interval = setInterval(() => {
    setCount(count + 1);
  }, 1000);
}, [count]);

// ✅ Correct - cleanup prevents memory leaks
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 1000);

return () => clearInterval(interval);
}, [count]);

When you forget cleanup functions, you're essentially telling the interviewer that your code would leak memory in production. That's a red flag for any senior role.

The cleanup function runs before the component unmounts and before the effect runs again due to dependency changes. Always ask yourself: "What resources am I creating that need to be cleaned up?"

Infinite Loop Traps: When Dependencies Go Wrong

This is where many candidates dig themselves into a hole during live coding sessions. Creating infinite loops with useEffect shows a fundamental misunderstanding of when effects run:

// ❌ Wrong - infinite loop because user object changes on every render
const [user, setUser] = useState({});

useEffect(() => {
// This creates a new object reference each time
setUser({ ...user, lastSeen: Date.now() });
}, [user]); // user changes every time, causing infinite loop

// ✅ Correct - only update when specific values change
const [user, setUser] = useState({});
const [lastSeen, setLastSeen] = useState(null);

useEffect(() => {
setLastSeen(Date.now());
}, [user.id]); // Only run when user ID actually changes

The key insight here is understanding object reference equality. When you include objects or arrays in your dependency array, make sure you're not creating new references in the effect that would trigger it again.

For complex objects, consider using useMemo or useCallback to stabilize references, or break down your state into primitive values when possible.

Race Conditions: Async Operations Done Wrong

Asynchronous operations in useEffect are where things get tricky, and it's a favorite interview topic because it tests your understanding of both React and JavaScript concurrency:

// ❌ Wrong - potential race condition
useEffect(() => {
  async function fetchData() {
    const result = await api.getUser(userId);
    setUser(result); // This might set stale data
  }
  fetchData();
}, [userId]);

// ✅ Correct - handle race conditions with cleanup
useEffect(() => {
let cancelled = false;

async function fetchData() {
const result = await api.getUser(userId);
if (!cancelled) {
setUser(result);
}
}

fetchData();

return () => {
cancelled = true;
};
}, [userId]);

Race conditions occur when a user quickly changes the userId prop, triggering multiple API calls. Without proper handling, a slower request might complete after a faster one, setting stale data.

The cancellation pattern shown above is one solution. You could also use AbortController for more sophisticated cancellation, but the key is showing the interviewer you're aware of the problem.

Wrong useEffect Patterns for Common Scenarios

Many candidates use useEffect when other hooks would be more appropriate. This reveals a shallow understanding of React's hook ecosystem:

// ❌ Wrong - using effect for derived state
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');

useEffect(() => {
setFullName(${firstName} ${lastName});
}, [firstName, lastName]);

// ✅ Correct - compute during render
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = ${firstName} ${lastName};

UseEffect is for side effects—operations that affect something outside the component. Deriving state from other state should happen during render, not in an effect.

Similarly, expensive calculations should use useMemo, and function definitions should use useCallback when they're dependencies of other hooks.

How to Ace useEffect Questions in Interviews

When you encounter useEffect questions in interviews, remember these key points:

  • Always think about the dependency array first - What values from component scope am I using?
  • Consider the cleanup - Am I creating any resources that need to be cleaned up?
  • Watch out for reference equality - Are my dependencies stable, or am I creating new references?
  • Handle async operations carefully - How do I prevent race conditions and stale state updates?
  • Question if useEffect is even needed - Would useMemo, useCallback, or computed values be more appropriate?
  • The interviewer isn't just testing your syntax knowledge—they want to see that you understand the implications of your choices and can write maintainable, bug-free React code.

    Mastering useEffect takes practice, especially under interview pressure. You need to internalize these patterns so they become second nature when you're coding on a whiteboard or shared screen.

    Practice this on Goliath Prep — AI-graded mock interviews with instant feedback. Try it free at app.goliathprep.com

    Practice Interview Questions with AI

    Goliath Prep gives you AI-powered mock interviews with instant feedback across 29+ technologies.

    Start Practicing Free →