One of the most frequently asked React interview questions is:

What is useEffect and why do we need it?

Most developers answer:

useEffect is used for API calls.

While API calls are a common use case, interviewers often go deeper:

  • What is a side effect?
  • When does useEffect run?
  • How does the dependency array work?
  • What are cleanup functions?
  • Can useEffect cause infinite loops?
  • What is the difference between useEffect and useLayoutEffect?

Let's understand useEffect from first principles.

1. Why Do We Need useEffect?#

React components should primarily focus on:

Rendering UI

Example:

JSX
function UserCard() {
  return <h1>John</h1>;
}

However, applications often need to perform operations such as:

  • API requests
  • Event listeners
  • Timers
  • Analytics tracking
  • WebSocket connections
  • DOM interactions

These operations are called:

Side Effects

2. What is a Side Effect?#

A side effect is any operation that interacts with something outside React's rendering process.

Examples:

JAVASCRIPT
fetch("/users");
JAVASCRIPT
setInterval(...);
JAVASCRIPT
window.addEventListener(...);
JAVASCRIPT
localStorage.setItem(...);

All of these are side effects.

3. Understanding the useEffect Hook#

useEffect is a React Hook that allows components to perform side effects after rendering.

Example:

JSX
useEffect(() => {
  console.log("Effect");
});

React executes the effect after rendering is complete.

Visual Representation#

Render
   ↓
Paint
   ↓
useEffect Runs

This sequence is important for interviews.

Basic Example#

JSX
function App() {
  useEffect(() => {
    console.log("Mounted");
  });

  return <h1>Hello</h1>;
}

Output:

Mounted

after rendering occurs.

How useEffect Works#

Syntax:

JSX
useEffect(() => {
  // Side Effect
});

Structure:

JSX
useEffect(effectFunction, dependencies);

4. Controlling Execution: The Dependency Array#

The dependency array controls when the effect runs.

Example:

JSX
useEffect(() => {
  console.log("Effect");
}, []);

Understanding dependency arrays is one of the most important React interview topics.

Case 1: No Dependency Array#

Example:

JSX
useEffect(() => {
  console.log("Effect");
});

Runs:

Every Render

Example#

JSX
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("Effect");
  });

  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

Every click:

Render
 ↓
Effect Runs

Case 2: Empty Dependency Array#

Example:

JSX
useEffect(() => {
  console.log("Mounted");
}, []);

Runs:

Only Once
After Initial Mount

Common Use Cases#

Perfect for:

  • Initial API requests
  • Analytics initialization
  • Event listener setup
  • WebSocket connections

API Call Example#

JSX
useEffect(() => {
  fetchUsers();
}, []);

Runs only once when the component mounts.

Case 3: Specific Dependencies#

Example:

JSX
useEffect(() => {
  console.log("Count Changed");
}, [count]);

Runs whenever:

JAVASCRIPT
count;

changes.

Visual Representation#

count Changes
      ↓
Re-render
      ↓
Effect Runs

Multiple Dependencies#

Example:

JSX
useEffect(() => {
  console.log("Updated");
}, [count, user]);

Runs when:

JAVASCRIPT
count;

or

JAVASCRIPT
user;

changes.

5. Resource Management: Cleanup Functions#

One of the most important useEffect concepts.

Example:

JSX
useEffect(() => {
  console.log("Effect");

  return () => {
    console.log("Cleanup");
  };
}, []);

What is a Cleanup Function?#

A cleanup function allows React to remove resources created by an effect.

Common examples:

  • Timers
  • Event listeners
  • Subscriptions
  • WebSocket connections

Timer Example#

Bad:

JSX
useEffect(() => {
  setInterval(() => {
    console.log("Running");
  }, 1000);
}, []);

Problem:

Interval Never Stops

Correct#

JSX
useEffect(() => {
  const id = setInterval(() => {
    console.log("Running");
  }, 1000);

  return () => {
    clearInterval(id);
  };
}, []);

Event Listener Example#

Bad:

JSX
useEffect(() => {
  window.addEventListener("resize", handleResize);
}, []);

Problem#

Listener Remains
After Component Removal

Correct#

JSX
useEffect(() => {
  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);

When Does Cleanup Run?#

Interviewers frequently ask this.

Cleanup runs:

Before Unmount#

Component Removed

Before The Next Effect#

Example:

JSX
useEffect(() => {
  console.log("Effect");

  return () => {
    console.log("Cleanup");
  };
}, [count]);

When:

JAVASCRIPT
count;

changes:

Output:

Cleanup
Effect

6. Common Pitfall: Infinite Loops#

A common interview question.

Bad:

JSX
const [count, setCount] = useState(0);

useEffect(() => {
  setCount(count + 1);
});

Result:

Render
 ↓
Effect
 ↓
State Update
 ↓
Render
 ↓
Effect

Infinite loop.

Why Does This Happen?#

Because:

State Update
      ↓
Re-render
      ↓
Effect Runs Again

repeats forever.

Fix#

Example:

JSX
useEffect(() => {
  setCount(1);
}, []);

Runs only once.

7. Common Pitfall: Stale Closures & Missing Dependencies#

Example:

JSX
useEffect(() => {
  console.log(user);
}, []);

Problem:

JAVASCRIPT
user;

is used but not included.

Potential issue:

Stale Data

Correct#

JSX
useEffect(() => {
  console.log(user);
}, [user]);

Stale Closure Example#

A common senior-level topic.

Example:

JSX
useEffect(() => {
  const id = setInterval(() => {
    console.log(count);
  }, 1000);

  return () => clearInterval(id);
}, []);

Problem:

count Never Updates

inside the interval.

Why?#

The effect captures:

JAVASCRIPT
count;

from the initial render.

This is called:

Stale Closure

8. Production Pattern: Fetch Requests & AbortController#

JSX
useEffect(() => {
  fetch("/users")
    .then((res) => res.json())
    .then(setUsers);
}, []);

Very common production usage.

AbortController Pattern#

Better:

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

  fetch("/users", {
    signal: controller.signal,
  });

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

Prevents unnecessary requests after unmounting.

9. useEffect vs. useLayoutEffect#

A common senior interview topic.

useEffect#

Runs:

After Paint

useLayoutEffect#

Runs:

Before Paint

and blocks rendering.

Visual Representation#

useEffect#

Render
 ↓
Paint
 ↓
Effect

useLayoutEffect#

Render
 ↓
Layout Effect
 ↓
Paint

10. React 18 Strict Mode Effect Behavior#

In development:

JSX
<React.StrictMode>

may execute effects twice.

Example:

JSX
useEffect(() => {
  console.log("Mounted");
}, []);

Development:

Mounted
Mounted

Production:

Mounted

11. Common Interview Q&A#

What is useEffect?#

A hook for performing side effects after rendering.

What Are Side Effects?#

Operations that interact with systems outside React rendering.

Examples:

  • API calls
  • Timers
  • Event listeners

When Does useEffect Run?#

After React completes rendering and painting.

What Does an Empty Dependency Array Mean?#

Run Once After Mount

What Happens Without a Dependency Array?#

Runs After Every Render

When Does Cleanup Run?#

  • Before unmount
  • Before the next effect executes

What Causes Infinite Loops in useEffect?#

State updates that continuously trigger re-renders.

What Is a Stale Closure?#

An effect capturing outdated values from previous renders.

Dependency Array Summary#

Dependency ArrayBehavior
No ArrayEvery Render
[]Initial Mount Only
[count]When count Changes
[count, user]When Any Dependency Changes

Senior-Level Interview Answer#

useEffect is a React Hook used for performing side effects after rendering. Common side effects include API requests, timers, subscriptions, event listeners, and browser storage interactions. The dependency array determines when the effect executes. An empty dependency array causes the effect to run only after the initial mount, while specified dependencies cause re-execution when those values change. Effects can also return cleanup functions that run before unmounting or before subsequent effect executions, helping prevent memory leaks and stale subscriptions. Understanding dependency management, cleanup behavior, and stale closures is essential for building reliable React applications.

Common Interview Mistakes#

❌ useEffect Is Only For API Calls#

Correct:

useEffect handles all kinds
of side effects.

❌ Cleanup Runs Only On Unmount#

Correct:

Cleanup also runs before
the next effect executes.

❌ Empty Dependency Arrays Are Always Correct#

Correct:

Dependencies should reflect
values used inside the effect.

❌ useEffect Runs Before Render#

Correct:

Render Happens First.

Effect Runs After Paint.

Key Takeaways#

  • Side Effects: Operations that interact with the outside world (APIs, timers, storage) outside the render pipeline.
  • Rendering Sequence: Effects run asynchronously after the render is committed and painted to the screen.
  • Dependency Array: Controls execution; empty [] runs once, populated [deps] runs on changes, omitted runs on every render.
  • Cleanup Return: Executes before the component unmounts and before the next effect run, preventing memory leaks.
  • Stale Closures: Occur when dependencies are missing, capturing variables from older render scopes.