Back to Blog
ReactHooksInterview Prep

Mastering React Hooks: The Complete Interview Guide

May 28, 2026·10 min read

React Hooks were introduced in v16.8 and have since become the dominant way to write React. If you are preparing for a frontend or full-stack interview at any company that uses React, understanding hooks at a deep level is non-negotiable. Interviewers do not just want to see that you know the API, they want to see that you understand why each hook exists and when to reach for it.

This guide walks through the hooks that come up most frequently in technical interviews, explains the tricky parts that trip candidates up, and lists the exact questions you are likely to face.

1. useState, State Management Fundamentals

useState is the first hook most developers learn, but interviews regularly reveal shallow understanding. The two things that most often catch candidates off guard are functional updates and lazy initialization.

Functional Updates

When the new state depends on the previous state, always use the functional form of the setter. Passing a value directly can cause stale reads when multiple updates are batched.

javascript
// ❌ Risky, may read stale state
setCount(count + 1);

// ✅ Safe, always operates on latest value
setCount(prev => prev + 1);

// Real-world example: incrementing inside a callback
function handleClick() {
  // If handleClick is called multiple times before React flushes,
  // the functional form guarantees correct results.
  setCount(prev => prev + 1);
}

Lazy Initialization

If the initial state is expensive to compute (e.g. reading from localStorage, parsing a large object), pass a function to useState instead of the value. React calls it only once on mount, not on every re-render.

javascript
// ❌ Runs on every render
const [data, setData] = useState(expensiveCompute());

// ✅ Runs only once
const [data, setData] = useState(() => expensiveCompute());

// Practical example, reading persisted state
const [theme, setTheme] = useState(
  () => localStorage.getItem('theme') ?? 'light'
);
Interview tip: When asked about useState, mention lazy initialization unprompted. Most candidates don't, it immediately signals a deeper understanding of React's render cycle.

2. useEffect, Side Effects and the Dependency Array

useEffect is the hook with the most failure modes, which makes it a goldmine for interviewers. The three areas they focus on are the dependency array, cleanup functions, and stale closures.

The Dependency Array

javascript
// Runs after every render
useEffect(() => { ... });

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

// Runs when userId or token changes
useEffect(() => {
  fetchUser(userId, token);
}, [userId, token]);

The rule of thumb is simple: every reactive value used inside the effect must be in the dependency array. Omitting a dependency does not make the effect run less, it just makes it read a stale value.

Cleanup Functions

The function returned from useEffect runs before the next effect fires and on unmount. Forgetting cleanup is one of the most common sources of memory leaks in React apps.

javascript
useEffect(() => {
  const controller = new AbortController();

  async function load() {
    try {
      const res = await fetch('/api/data', {
        signal: controller.signal,
      });
      setData(await res.json());
    } catch (err) {
      if (err.name !== 'AbortError') setError(err);
    }
  }

  load();

  // Cleanup: cancel the request if the component unmounts
  // or if the effect re-runs before the fetch completes.
  return () => controller.abort();
}, []);

Stale Closures

A stale closure happens when an effect captures a variable at the time it was created but the variable has since changed. This is a classic interview question.

javascript
// ❌ Stale closure, count is always 0 inside the interval
useEffect(() => {
  const id = setInterval(() => {
    console.log(count); // always logs 0
  }, 1000);
  return () => clearInterval(id);
}, []); // count is missing from deps

// ✅ Fix 1, add count to deps (re-registers interval each update)
useEffect(() => {
  const id = setInterval(() => {
    console.log(count);
  }, 1000);
  return () => clearInterval(id);
}, [count]);

// ✅ Fix 2, functional update avoids needing count at all
useEffect(() => {
  const id = setInterval(() => {
    setCount(prev => prev + 1);
  }, 1000);
  return () => clearInterval(id);
}, []);
Common mistake:Candidates often say “I leave the dependency array empty to prevent infinite loops.” That is a red flag. The correct answer is to restructure the effect so it does not produce a loop, not to suppress the dependency.

3. useMemo, Memoizing Expensive Computations

useMemo caches the result of a computation between renders. It only recalculates when one of its dependencies changes.

javascript
const filteredList = useMemo(
  () => items.filter(item => item.active && item.score > threshold),
  [items, threshold]
);

The interview question you will be asked: “Should you wrap every computation in useMemo?” The answer is no. useMemo itself has a cost, it allocates memory and runs a comparison on every render. Only use it when:

  • The calculation is demonstrably slow (large array operations, complex math)
  • The result is passed to a memoized child component as a prop
  • The result is used as a dependency in another hook

For simple derivations like items.length or filtering a list of ten items, skipping useMemo is the right call.

4. useCallback, Stable Function References

useCallback returns a memoized version of a callback. It solves a specific problem: in React, a function defined inside a component is re-created on every render, producing a new reference each time.

javascript
// Without useCallback, new reference on every render
function Parent() {
  const handleClick = () => doSomething(); // new fn each render

  return <MemoizedChild onClick={handleClick} />;
  // React.memo on Child is useless, onClick is always "new"
}

// With useCallback, stable reference between renders
function Parent() {
  const handleClick = useCallback(() => doSomething(), []);

  return <MemoizedChild onClick={handleClick} />;
  // Now React.memo can actually bail out of re-rendering
}
Key distinction interviewers test: useMemo memoizes a computed value. useCallback memoizes a function reference. In fact, useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

5. Custom Hooks, What Interviewers Really Want to See

Custom hooks are just functions that start with use and call other hooks. They let you extract reusable stateful logic from components. This topic comes up in almost every senior React interview because it tests both hook knowledge and component design thinking.

A good example to bring up: a useFetch hook that encapsulates loading, error, and data state.

javascript
function useFetch(url) {
  const [state, setState] = useState({
    data: null,
    loading: true,
    error: null,
  });

  useEffect(() => {
    if (!url) return;

    const controller = new AbortController();
    setState({ data: null, loading: true, error: null });

    fetch(url, { signal: controller.signal })
      .then(res => {
        if (!res.ok) throw new Error(res.statusText);
        return res.json();
      })
      .then(data => setState({ data, loading: false, error: null }))
      .catch(err => {
        if (err.name === 'AbortError') return;
        setState({ data: null, loading: false, error: err });
      });

    return () => controller.abort();
  }, [url]);

  return state;
}

// Usage, clean component with zero fetch boilerplate
function UserProfile({ userId }) {
  const { data, loading, error } = useFetch(`/api/users/${userId}`);
  if (loading) return <Spinner />;
  if (error)   return <Error message={error.message} />;
  return <div>{data.name}</div>;
}

When you present a custom hook in an interview, walk through three things: what state it manages, why the cleanup function is necessary, and how it keeps the consuming component clean.

6. Top Interview Questions, With Model Answers

Q: What is the difference between useCallback and useMemo?

Both memoize something across renders. useMemo caches the return value of a function. useCallback caches the function itself. Use useMemo for expensive calculations; use useCallback when you need a stable function reference to pass as a prop to a memoized child or use as a dependency.

Q: When would you NOT use useMemo?

When the computation is cheap (simple arithmetic, property access, short array operations), when the dependency array changes on almost every render anyway (making the cache useless), or when premature optimization would obscure the code. Always profile before optimizing.

Q: What happens if you omit a dependency from useEffect?

The effect runs based on an incomplete picture of its reactive inputs. The callback captures the values as they were on the render where it was last registered, a stale closure. If the omitted value later changes, the effect does not re-run and reads old data.

Q: Why do we need the cleanup function in useEffect?

Without cleanup, subscriptions, timers, and pending async operations can outlive the component that created them. When the component unmounts (or before the effect re-runs), React calls the cleanup function to cancel or unsubscribe. This prevents memory leaks, double-registrations, and setting state on unmounted components.

Q: Can you call hooks conditionally?

No. Hooks must be called in the same order on every render. React uses call order to associate hook state with the correct hook instance. A conditional hook call would break that ordering and produce incorrect state. Move the condition inside the hook body instead.


Ready to test your knowledge? QuizVertex has a full React quiz track with 20 questions across four difficulty levels , Trainee through Senior. Each question comes with a detailed explanation so you know exactly why an answer is right or wrong. Practice React questions →

Quick Reference Cheat Sheet

  • useState, local component state; use the functional setter form when the new value depends on the old one
  • useEffect, synchronize with external systems; always declare every reactive value in deps; always return cleanup
  • useMemo, cache an expensive computed value; only use when the calculation is provably slow or the result is a dep of another hook
  • useCallback, cache a function reference; mainly useful when passing callbacks to memoized children or as effect deps
  • Custom hooks, start with use, can call other hooks, extract reusable stateful logic from components
  • Rules of hooks, only call at the top level, only call inside React functions, never inside conditions or loops
Enjoyed this guide?

Test what you just learned with a timed React quiz.