React Hooks Interview Questions You'll Actually Be Asked

·10 min read
reactinterview-questionshooksfrontendjavascript

According to the 2024 State of JS survey, 99% of React developers use hooks—yet "explain when to use useCallback vs useMemo" remains one of the most commonly failed interview questions. The gap between using hooks and understanding them is where most candidates stumble. Here's how to stand out.

The 30-Second Answer

When the interviewer asks "What are React Hooks?", here's your concise answer:

"Hooks are functions that let you use React features like state and lifecycle methods in functional components. The most important ones are useState for local state, useEffect for side effects, and useContext for accessing context. They were introduced in React 16.8 and have largely replaced class components."

Wait for follow-up questions. Don't ramble about every hook you know.

The 2-Minute Answer (If They Want More)

If they ask you to elaborate:

"Before Hooks, you needed class components to use state or lifecycle methods. This led to complex patterns like render props and higher-order components for sharing logic.

Hooks solve this by letting you extract stateful logic into reusable functions called custom hooks. The key hooks are:

  • useState for local component state
  • useEffect for side effects like data fetching or subscriptions
  • useContext for consuming React Context
  • useRef for mutable values that don't trigger re-renders
  • useMemo and useCallback for performance optimization

The rules are simple: call hooks at the top level, and only in React components or custom hooks. This ensures React can track hook state correctly across renders."

Code Examples to Show

useState - Basic and Functional Updates

// Basic usage
const [count, setCount] = useState(0);
 
// Object state
const [user, setUser] = useState({ name: '', email: '' });
 
// Updating object state (always create new object)
setUser(prev => ({ ...prev, name: 'John' }));
 
// Functional update (when new state depends on old)
setCount(prev => prev + 1);
 
// Lazy initial state (expensive computation)
const [data, setData] = useState(() => computeExpensiveValue());

useEffect - The Dependency Array

// Runs after every render
useEffect(() => {
  console.log('Every render');
});
 
// Runs once on mount
useEffect(() => {
  console.log('Mount only');
}, []);
 
// Runs when dependencies change
useEffect(() => {
  console.log('userId changed');
  fetchUser(userId);
}, [userId]);
 
// With cleanup
useEffect(() => {
  const subscription = subscribeToData(id);
 
  return () => {
    subscription.unsubscribe(); // Cleanup
  };
}, [id]);

The Classic Problem: useEffect Infinite Loop

This mistake appears in almost every React interview:

// BUG: Infinite loop!
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
 
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }); // Missing dependency array!
 
  return <div>{user?.name}</div>;
}

What happens?

  1. Component renders
  2. useEffect runs, fetches data
  3. setUser triggers re-render
  4. useEffect runs again (no deps = runs every render)
  5. Infinite loop!

Three Ways to Fix It

1. Add dependency array (correct solution)

useEffect(() => {
  fetch(`/api/users/${userId}`)
    .then(res => res.json())
    .then(data => setUser(data));
}, [userId]); // Only re-run when userId changes

2. Empty array for mount-only

useEffect(() => {
  fetchInitialData();
}, []); // Runs once on mount

3. Use a flag to prevent duplicate fetches

useEffect(() => {
  let cancelled = false;
 
  fetch(`/api/users/${userId}`)
    .then(res => res.json())
    .then(data => {
      if (!cancelled) setUser(data);
    });
 
  return () => { cancelled = true; }; // Cleanup
}, [userId]);

The Question That Separates Junior from Senior: useMemo vs useCallback

Interviewers love this because most candidates mix them up.

// useCallback - memoizes a FUNCTION
const handleClick = useCallback(() => {
  console.log('Clicked:', id);
}, [id]);
 
// useMemo - memoizes a VALUE
const expensiveResult = useMemo(() => {
  return computeExpensiveValue(data);
}, [data]);

When to Use Each

useCallback - when passing functions to child components:

function Parent() {
  const [count, setCount] = useState(0);
 
  // Without useCallback, this creates a new function every render
  // causing Child to re-render unnecessarily
  const handleIncrement = useCallback(() => {
    setCount(c => c + 1);
  }, []);
 
  return <Child onIncrement={handleIncrement} />;
}
 
const Child = React.memo(({ onIncrement }) => {
  console.log('Child rendered');
  return <button onClick={onIncrement}>+</button>;
});

useMemo - for expensive calculations:

function FilteredList({ items, filter }) {
  // Only recalculates when items or filter change
  const filteredItems = useMemo(() => {
    return items.filter(item =>
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);
 
  return <List items={filteredItems} />;
}

The Trap: Over-Optimization

// DON'T do this - premature optimization
const value = useMemo(() => a + b, [a, b]); // Simple math doesn't need memoization
 
// DO use it for actual expensive operations
const sorted = useMemo(() =>
  [...largeArray].sort((a, b) => a.value - b.value),
  [largeArray]
);

Custom Hooks: The Senior Developer Question

Writing a custom hook in an interview shows you understand hooks deeply.

Example: useLocalStorage

function useLocalStorage(key, initialValue) {
  // Get initial value from localStorage or use default
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });
 
  // Update localStorage when state changes
  const setValue = useCallback((value) => {
    try {
      const valueToStore = value instanceof Function
        ? value(storedValue)
        : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  }, [key, storedValue]);
 
  return [storedValue, setValue];
}
 
// Usage
function App() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
 
  return (
    <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
      Current: {theme}
    </button>
  );
}

Example: useFetch

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
 
  useEffect(() => {
    let cancelled = false;
    setLoading(true);
 
    fetch(url)
      .then(res => {
        if (!res.ok) throw new Error('Network error');
        return res.json();
      })
      .then(data => {
        if (!cancelled) {
          setData(data);
          setLoading(false);
        }
      })
      .catch(err => {
        if (!cancelled) {
          setError(err.message);
          setLoading(false);
        }
      });
 
    return () => { cancelled = true; };
  }, [url]);
 
  return { data, loading, error };
}
 
// Usage
function UserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
 
  if (loading) return <Spinner />;
  if (error) return <Error message={error} />;
  return <Profile user={user} />;
}

Common Follow-Up Questions

"What is useRef and when do you use it?"

"useRef returns a mutable object with a .current property that persists across renders without causing re-renders when changed. Two main uses:

  1. Accessing DOM elements directly
  2. Storing mutable values that don't need to trigger re-renders (like timer IDs)"
// DOM access
function TextInput() {
  const inputRef = useRef(null);
 
  const focusInput = () => inputRef.current.focus();
 
  return <input ref={inputRef} />;
}
 
// Mutable value without re-render
function Timer() {
  const intervalRef = useRef(null);
 
  const start = () => {
    intervalRef.current = setInterval(() => {
      console.log('tick');
    }, 1000);
  };
 
  const stop = () => clearInterval(intervalRef.current);
 
  return <button onClick={start}>Start</button>;
}

"What's the difference between useEffect and useLayoutEffect?"

"Both run after render, but useLayoutEffect runs synchronously before the browser paints. Use useLayoutEffect when you need to measure DOM elements or make visual changes that must happen before the user sees anything. For most cases, useEffect is correct and more performant."

// useLayoutEffect - runs before paint
useLayoutEffect(() => {
  // Measure element, adjust position
  const height = ref.current.getBoundingClientRect().height;
  setPosition(calculatePosition(height));
}, []);
 
// useEffect - runs after paint (default choice)
useEffect(() => {
  // Fetch data, set up subscriptions
  fetchData();
}, []);

"How do you handle multiple pieces of related state?"

"Three approaches depending on complexity:

  1. Multiple useState calls for independent values
  2. Single useState with an object for related values
  3. useReducer for complex state with interdependent updates"
// Multiple useState - simple, independent values
const [name, setName] = useState('');
const [email, setEmail] = useState('');
 
// Object state - related values
const [form, setForm] = useState({ name: '', email: '' });
const updateField = (field, value) =>
  setForm(prev => ({ ...prev, [field]: value }));
 
// useReducer - complex, interdependent updates
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'SUBMIT_FORM' });

What Interviewers Are Really Testing

When I ask about hooks, I'm checking:

  1. Basic knowledge - Do you know useState and useEffect?
  2. Dependency array understanding - Can you prevent infinite loops?
  3. Optimization judgment - Do you know when to use useMemo/useCallback (not just how)?
  4. Custom hooks - Can you extract reusable logic?
  5. Real-world thinking - Do you consider cleanup, race conditions, error handling?

A candidate who shows the cleanup pattern, explains when (not just how) to optimize, and can write a custom hook will stand out.

Quick Reference Card

HookPurposeKey Point
useStateLocal stateUse functional updates for state based on prev
useEffectSide effectsAlways consider cleanup and deps
useContextConsume contextTriggers re-render when context changes
useRefDOM/mutable valuesChanges don't cause re-renders
useMemoMemoize valuesFor expensive calculations
useCallbackMemoize functionsFor stable references to child components
useReducerComplex stateWhen state logic is complex
useLayoutEffectSync after renderFor DOM measurements before paint

Practice Questions

Test yourself before your interview:

1. What's wrong with this code?

function SearchResults({ query }) {
  const [results, setResults] = useState([]);
 
  useEffect(async () => {
    const data = await fetchResults(query);
    setResults(data);
  }, [query]);
 
  return <List items={results} />;
}

2. Will the Child component re-render when Parent re-renders? How would you fix it?

function Parent() {
  const [count, setCount] = useState(0);
 
  const handleClick = () => console.log('clicked');
 
  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <Child onClick={handleClick} />
    </>
  );
}
 
const Child = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click me</button>;
});

3. Implement a useToggle custom hook that returns [value, toggle].

Answers:

  1. useEffect callback can't be async directly. Wrap in an IIFE or define async function inside.
  2. Yes, Child re-renders because handleClick is recreated each render. Fix with useCallback.
  3. const useToggle = (initial = false) => { const [value, setValue] = useState(initial); const toggle = useCallback(() => setValue(v => !v), []); return [value, toggle]; }

Related Articles

If you found this helpful, check out these related guides:


Ready for More React Interview Questions?

This is just one topic from our complete React interview prep guide. Get access to 50+ React questions covering:

  • Virtual DOM and Reconciliation
  • Component patterns and composition
  • State management (Context, Redux, Zustand)
  • Performance optimization
  • Testing strategies

Get Full Access to All React Questions →

Or try our free React preview to see more questions like this.


Written by the EasyInterview team, based on real interview experience from 12+ years in tech and hundreds of technical interviews conducted at companies like BNY Mellon, UBS, and leading fintech firms.

Ready to ace your interview?

Get 550+ interview questions with detailed answers in our comprehensive PDF guides.

View PDF Guides