How does the useEffect dependency array work?

React

The short answer

The dependency array is the second argument to useEffect. It tells React when to re-run the effect. If any value in the array changes between renders, the effect runs again. If the array is empty, the effect only runs once on mount. If you skip the array entirely, the effect runs after every render.

The three patterns

1. No dependency array — runs after every render:

useEffect(() => {
console.log('Runs after every render');
});

This is rarely what you want. The effect runs after the initial render and after every re-render.

2. Empty dependency array — runs once on mount:

useEffect(() => {
console.log('Runs only once');
}, []);

The effect runs after the first render only. This is commonly used for fetching data, setting up subscriptions, or initializing something once.

3. With dependencies — runs when dependencies change:

useEffect(() => {
console.log(`User changed to: ${userId}`);
fetchUser(userId);
}, [userId]);

The effect runs after the first render and again whenever userId changes.

How React checks dependencies

React uses Object.is to compare each dependency with its value from the previous render. If any value is different, the effect re-runs.

function Profile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => setUser(data));
}, [userId]);
// Render 1: userId = 1 → effect runs, fetches user 1
// Render 2: userId = 1 → effect does NOT run (same value)
// Render 3: userId = 2 → effect runs, fetches user 2
}

The cleanup function

When an effect re-runs, React calls the cleanup function from the previous effect first:

useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
// Cleanup: runs before the next effect and on unmount
return () => {
connection.disconnect();
};
}, [roomId]);

Here is the order:

  1. Component mounts → effect runs, connects to room 1
  2. roomId changes to 2 → cleanup runs (disconnects from room 1) → effect runs (connects to room 2)
  3. Component unmounts → cleanup runs (disconnects from room 2)

Common mistakes

Mistake 1: Missing dependencies

// Bad — count is used inside but not in the dependency array
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // uses stale count
}, 1000);
return () => clearInterval(id);
}, []);
// Good — use the functional updater
useEffect(() => {
const id = setInterval(() => {
setCount((c) => c + 1); // always gets latest value
}, 1000);
return () => clearInterval(id);
}, []);

Mistake 2: Object or array dependencies

// Bad — new object on every render, effect runs every time
function Profile({ userId }) {
const options = { userId, includeAvatar: true };
useEffect(() => {
fetchProfile(options);
}, [options]); // options is a new object every render!
}
// Good — use primitive values or useMemo
useEffect(() => {
fetchProfile({ userId, includeAvatar: true });
}, [userId]); // userId is a primitive, stable reference

Objects and arrays are compared by reference, not by value. A new object {} is never equal to another {} even if they have the same content.

Mistake 3: Infinite loops

// Infinite loop!
useEffect(() => {
setCount(count + 1); // triggers re-render → effect runs again → ...
}, [count]);

The effect depends on count and also changes count. This creates an endless loop.

Common Pitfalls

The most common mistake with dependency arrays is lying about dependencies — putting an empty array when the effect actually uses values from the component. This causes stale closures where the effect uses old values. React's ESLint plugin (react-hooks/exhaustive-deps) will warn you about this. Always trust the linter and fix the warnings instead of ignoring them.

Interview Tip

When explaining the dependency array, walk through all three patterns (no array, empty array, with values) and give a concrete example for each. The cleanup function is often a follow-up question — explain the order of execution. If you can also explain why objects cause issues as dependencies (reference vs value comparison), that shows a strong understanding.

Why interviewers ask this

The dependency array is the most error-prone part of useEffect. Interviewers ask about it to see if you understand when effects re-run, if you know about stale closures, and if you can handle common pitfalls like object dependencies and infinite loops. These are real bugs that come up in every React codebase.