Skip to main content

🧠 React useEffect — In-Depth Theory Guide


1. Introduction

🔹 What is useEffect?

useEffect is a React Hook used to handle side effects in functional components. A side effect is anything that:
  • Interacts with the outside world
  • Happens outside the render cycle
Examples:
  • API calls
  • DOM manipulation
  • Subscriptions (WebSocket, event listeners)
  • Timers (setTimeout, setInterval)
  • Logging
👉 In simple terms:
useEffect lets you run code after React renders your component

🔹 Why is it important in React?

React components are expected to be pure functions:
  • Same input → same output (UI)
But real apps need:
  • Data fetching
  • Event handling
  • External sync
useEffect bridges:
Pure UI rendering ↔ Imperative side effects
Without useEffect, handling real-world behavior would be messy and unpredictable.

🔹 When and why we use it

Use useEffect when you need to:

✅ Synchronize with external systems

  • APIs
  • Browser APIs
  • Third-party libraries

✅ React to state or prop changes

  • Fetch new data when ID changes
  • Update title when state updates

✅ Manage lifecycle-like behavior

  • Component mount
  • Component update
  • Component unmount

2. Concepts / Internal Workings


🔹 Core Concept: Effects run after render

React lifecycle (functional component):
  1. Render phase (pure)
  2. Commit phase (DOM updated)
  3. Effect phase (useEffect runs here)
👉 Effects never block rendering

🔹 Types of Effects

1. Passive Effects (useEffect)

  • Run after paint
  • Non-blocking
  • Used for most cases

2. Layout Effects (useLayoutEffect)

  • Run before paint
  • Blocking
  • Used for DOM measurement

🔹 Dependency Array (Critical Concept)

useEffect(() => {
  // effect logic
}, [dependencies]);

Behavior:

DependencyBehavior
[]Runs once (on mount)
[x]Runs when x changes
no arrayRuns on every render

🔹 Closures & Stale Data

Effects capture values from the render they were created in.
useEffect(() => {
  console.log(count);
}, []);
⚠️ Problem:
  • count will always be the initial value (stale closure)

🔹 Cleanup Function

useEffect(() => {
  const id = setInterval(() => {}, 1000);

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

Why cleanup matters:

  • Prevent memory leaks
  • Remove subscriptions
  • Avoid duplicate side effects

🔹 Internal Working (Simplified)

React internally:
  1. Stores effects in a queue during render
  2. After commit:
    • Runs cleanup (if exists)
    • Runs new effect
👉 Order:
Render → Commit → Cleanup → Effect

🔹 Relationship with Other React Features

1. useState

  • Effects often depend on state changes

2. useRef

  • Used to persist values across renders without triggering effects

3. useMemo / useCallback

  • Prevent unnecessary effect re-runs

4. Concurrent Rendering (React 18+)

  • Effects may run more than once in dev (Strict Mode)
  • Helps detect unsafe side effects

3. Syntax & Examples


🔹 Basic Syntax

useEffect(() => {
  // side effect
}, [dependencies]);

🔹 1. Run Once (Component Mount)

useEffect(() => {
  console.log("Component mounted");
}, []);

🔹 2. Run on State Change

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

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

🔹 3. API Call Example

useEffect(() => {
  fetch("/api/data")
    .then(res => res.json())
    .then(data => console.log(data));
}, []);

🔹 4. Cleanup Example (Event Listener)

useEffect(() => {
  const handleResize = () => console.log(window.innerWidth);

  window.addEventListener("resize", handleResize);

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

🔹 5. Interval Example

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

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

🔹 6. Derived Effect (Multiple Dependencies)

useEffect(() => {
  console.log("User or filter changed");
}, [user, filter]);

🔹 7. Conditional Effect

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

  fetchUserData(user.id);
}, [user]);

🔹 8. Avoiding Stale Closures

useEffect(() => {
  const id = setInterval(() => {
    setCount(prev => prev + 1);
  }, 1000);

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

🔹 9. Splitting Effects (Best Pattern)

❌ Bad:
useEffect(() => {
  fetchData();
  document.title = "Hello";
}, []);
✅ Good:
useEffect(() => {
  fetchData();
}, []);

useEffect(() => {
  document.title = "Hello";
}, []);

4. Edge Cases / Common Mistakes


🔴 1. Missing Dependencies

useEffect(() => {
  fetchData(id);
}, []); // ❌ id missing
Problem:
  • Effect uses stale id
Fix:
}, [id]);

🔴 2. Infinite Loops

useEffect(() => {
  setCount(count + 1);
}, [count]);
👉 Causes infinite re-renders Fix:
  • Use condition OR rethink logic

🔴 3. Function Dependencies

useEffect(() => {
  fetchData();
}, [fetchData]); // ⚠️ unstable reference
Fix:
const fetchData = useCallback(() => {}, []);

🔴 4. Object/Array Dependencies

useEffect(() => {}, [{ a: 1 }]); // ❌ new reference every render
Fix:
  • Memoize or extract outside

🔴 5. Strict Mode Double Execution

In development:
useEffect(() => {
  console.log("Runs twice");
}, []);
👉 Expected in React 18 Strict Mode Purpose:
  • Detect side effects issues

🔴 6. Async Directly in Effect

useEffect(async () => {}); // ❌
Fix:
useEffect(() => {
  const fetchData = async () => {};
  fetchData();
}, []);

🔴 7. Race Conditions (API Calls)

useEffect(() => {
  fetch(`/api/${id}`).then(setData);
}, [id]);
Problem:
  • Older request may override new one
Fix:
  • Use abort controller or track latest request

5. Best Practices


✅ 1. Keep Effects Focused

👉 One effect = one responsibility

✅ 2. Always Declare Dependencies Correctly

  • Use ESLint plugin: react-hooks/exhaustive-deps

✅ 3. Prefer Derived State over Effects

❌ Avoid:
useEffect(() => {
  setFullName(first + last);
}, [first, last]);
✅ Prefer:
const fullName = first + last;

✅ 4. Use Cleanup Properly

  • Always clean:
    • timers
    • subscriptions
    • listeners

✅ 5. Avoid Overusing useEffect

👉 If it can be computed during render → don’t use effect

✅ 6. Memoize Dependencies When Needed

Use:
  • useCallback
  • useMemo

✅ 7. Handle Async Safely

useEffect(() => {
  let isMounted = true;

  fetchData().then(data => {
    if (isMounted) setData(data);
  });

  return () => {
    isMounted = false;
  };
}, []);

✅ 8. Think in Synchronization, Not Lifecycle

Instead of:
“Run this on mount”
Think:
“Keep this in sync with X”

✅ 9. Use Custom Hooks for Reusability

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handler = () => setWidth(window.innerWidth);
    window.addEventListener("resize", handler);

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

  return width;
}

🚀 Final Mental Model

useEffect is not about lifecycle — it’s about synchronizing your component with external systems after render

🧠 Advanced useEffect — Senior-Level Interview Questions & Answers


1. What problem does useEffect actually solve in React’s architecture?

✅ Answer

useEffect exists to separate pure rendering from side effects.

🔍 WHY this matters

React’s rendering model is based on pure functions:
UI = f(state, props)
But real applications require:
  • Network calls
  • DOM APIs
  • Subscriptions
These are impure operations. 👉 useEffect ensures:
  • Rendering stays pure
  • Side effects run after commit phase

🧠 Key Insight

useEffect is not about lifecycle — it’s about synchronization with external systems

2. Why are effects executed after the commit phase and not during render?

✅ Answer

Because React needs rendering to remain:
  • Deterministic
  • Interruptible (Concurrent Mode)

🔍 WHY

If effects ran during render:
  • They could block rendering
  • They could cause inconsistent UI
  • They would break concurrent rendering

🔄 Internal Flow

Render → Commit DOM → Run Effects

🧠 Insight

Effects are deferred to ensure React can prioritize UI updates without side-effect interference

3. How does React determine whether to re-run an effect?

✅ Answer

React performs a shallow comparison of dependency array values using Object.is.
useEffect(() => {}, [dep]);

🔍 WHY shallow comparison?

  • Performance: deep comparison is expensive
  • Predictability: reference equality is consistent

⚠️ Implication

useEffect(() => {}, [{ a: 1 }]); // always re-runs
Because:
  • New object reference each render

✅ Solution

const memoObj = useMemo(() => ({ a: 1 }), []);

4. What are “stale closures” in useEffect, and why do they occur?

✅ Answer

A stale closure happens when an effect captures outdated values from a previous render.
useEffect(() => {
  console.log(count);
}, []);

🔍 WHY it happens

  • Functions in React close over variables at render time
  • Effect runs later, but uses old values

🧠 Fix strategies

  1. Add dependency:
}, [count]);
  1. Functional updates:
setCount(prev => prev + 1);

🧠 Insight

Effects don’t “see” updates unless dependencies tell them to

5. Why does React Strict Mode run effects twice in development?

✅ Answer

To detect:
  • Unsafe side effects
  • Missing cleanup logic

🔍 WHY

React simulates:
  1. Mount
  2. Unmount
  3. Re-mount
useEffect(() => {
  console.log("Runs twice in dev");
}, []);

🧠 Insight

If your effect breaks under double execution → it’s not safe

6. What is the difference between useEffect and useLayoutEffect?

✅ Answer

FeatureuseEffectuseLayoutEffect
TimingAfter paintBefore paint
BlockingNoYes
Use caseAsync side effectsDOM measurement

🔍 WHY distinction exists

To allow:
  • Performance optimization (useEffect)
  • Layout correctness (useLayoutEffect)

Example

useLayoutEffect(() => {
  const rect = ref.current.getBoundingClientRect();
}, []);

7. Why is putting async directly in useEffect discouraged?

✅ Answer

Because useEffect expects:
  • A function OR
  • A cleanup function
But async returns a Promise.
useEffect(async () => {}); // ❌

🔍 Correct pattern

useEffect(() => {
  async function fetchData() {}
  fetchData();
}, []);

🧠 Insight

Cleanup must be synchronous → async breaks contract

8. How do race conditions occur in effects, and how do you prevent them?

✅ Answer

Race condition:
  • Multiple async calls → responses arrive out of order
useEffect(() => {
  fetch(`/api/${id}`).then(setData);
}, [id]);

🔍 Problem

  • Old request may overwrite new state

✅ Fix: AbortController

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

  fetch(`/api/${id}`, { signal: controller.signal })
    .then(res => res.json())
    .then(setData);

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

🧠 Insight

Effects must handle async cancellation explicitly

9. Why is splitting effects considered a best practice?

✅ Answer

Because effects represent independent synchronization processes.

❌ Bad

useEffect(() => {
  fetchData();
  document.title = "Hello";
}, []);

✅ Good

useEffect(fetchData, []);
useEffect(() => {
  document.title = "Hello";
}, []);

🔍 WHY

  • Better separation of concerns
  • Easier debugging
  • Correct dependency tracking

10. When should you NOT use useEffect?

✅ Answer

When logic can be computed during render.

❌ Bad

useEffect(() => {
  setFullName(first + last);
}, [first, last]);

✅ Good

const fullName = first + last;

🧠 Insight

Effects are for side effects, not derived state

11. Why can functions in dependency arrays cause unnecessary re-renders?

✅ Answer

Because functions are recreated every render.
useEffect(() => {}, [handleClick]);

🔍 WHY

  • New reference → dependency changed → effect runs again

✅ Fix

const handleClick = useCallback(() => {}, []);

12. How does React schedule and clean up effects internally?

✅ Answer

React maintains:
  • Effect list per fiber

Execution order:

  1. Cleanup previous effect
  2. Run new effect
RenderCommitCleanupEffect

🧠 Insight

Cleanup always runs before next effect, not just on unmount

13. What happens if you omit dependencies entirely?

✅ Answer

useEffect(() => {});
Runs on:
  • Every render

🔍 WHY

React assumes:
  • Effect depends on everything

⚠️ Risk

  • Performance issues
  • Infinite loops

14. How do you debug an infinite loop caused by useEffect?

✅ Answer

Step-by-step:

  1. Identify state updates inside effect
  2. Check dependencies
useEffect(() => {
  setCount(count + 1);
}, [count]);

🔍 Fix strategies

  • Add condition
  • Remove unnecessary state updates
  • Use functional updates

15. What is the conceptual difference between lifecycle methods and useEffect?

✅ Answer

LifecycleuseEffect
ImperativeDeclarative
Time-basedDependency-based

🔍 WHY React moved away

Class lifecycle:
  • Fragmented logic
  • Hard to reason about
Hooks:
  • Co-locate logic by concern

🧠 Insight

useEffect expresses “sync with X” instead of “run at time Y”

16. How do you handle subscriptions correctly in useEffect?

✅ Answer

Always:
  • Subscribe in effect
  • Unsubscribe in cleanup
useEffect(() => {
  socket.subscribe();

  return () => socket.unsubscribe();
}, []);

🔍 WHY

  • Prevent memory leaks
  • Avoid duplicate listeners

17. What are the trade-offs of using useEffect heavily?

✅ Answer

Pros:

  • Flexible
  • Handles all side effects

Cons:

  • Hard to reason about dependencies
  • Risk of bugs (stale closures, loops)
  • Overuse leads to messy code

🧠 Alternative approaches

  • Derived state
  • Event handlers
  • Custom hooks
  • Server-side data fetching (Next.js)

18. How do custom hooks improve useEffect usage?

✅ Answer

They:
  • Encapsulate effect logic
  • Improve reusability
  • Hide complexity

Example

function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);

  useEffect(() => {
    const update = () => setIsOnline(navigator.onLine);

    window.addEventListener("online", update);
    window.addEventListener("offline", update);

    return () => {
      window.removeEventListener("online", update);
      window.removeEventListener("offline", update);
    };
  }, []);

  return isOnline;
}

🚀 Final Takeaway

Senior-level understanding of useEffect is not about syntax — it’s about thinking in synchronization, managing side effects safely, and avoiding unnecessary effects altogether

🧠 Advanced useEffect — Senior-Level MCQs


1. What will be logged and why?

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

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

  return <button onClick={() => setCount(5)}>Click</button>;
}

Options:

A. 0 on mount, then 5 after click B. Only 0 C. Only 5 D. Nothing logged

✅ Correct Answer: B

💡 Explanation:

  • Effect runs only once ([])
  • Closure captures count = 0
  • Even after state updates, effect does NOT re-run

❌ Why others are wrong:

  • A: Effect doesn’t re-run after click
  • C: Effect already executed before update
  • D: Effect runs on mount

2. What happens here?

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

Options:

A. Runs once B. Runs twice C. Infinite loop D. Throws error

✅ Correct Answer: C

💡 Explanation:

  • Updating count triggers re-render
  • Dependency changes → effect runs again
  • Loop continues indefinitely

❌ Why others are wrong:

  • A/B: No stopping condition
  • D: No runtime error, just loop

3. Which fix prevents unnecessary effect re-runs?

useEffect(() => {
  fetchData();
}, [fetchData]);

Options:

A. Remove dependency array B. Wrap fetchData in useMemo C. Wrap fetchData in useCallback D. Move fetchData inside effect

✅ Correct Answer: C (Best)

💡 Explanation:

  • Functions change reference every render
  • useCallback stabilizes reference

⚖️ Nuance:

  • D also works, but not always ideal (less reusable)

❌ Why others are wrong:

  • A: Runs every render
  • B: useMemo is for values, not functions

4. What happens in Strict Mode (React 18)?

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

Options:

A. Runs once B. Runs twice (dev only) C. Runs twice (prod + dev) D. Runs randomly

✅ Correct Answer: B

💡 Explanation:

  • Strict Mode intentionally double-invokes effects
  • Helps detect unsafe side effects

5. What is the issue here?

useEffect(() => {
  fetch(`/api/${id}`).then(setData);
}, []);

Options:

A. Missing dependency B. Memory leak C. Infinite loop D. Syntax error

✅ Correct Answer: A

💡 Explanation:

  • id is used but not declared as dependency
  • Effect won’t update when id changes

6. What is the main risk here?

useEffect(() => {
  const id = setInterval(() => {
    console.log("tick");
  }, 1000);
}, []);

Options:

A. Infinite loop B. Memory leak C. Performance optimization D. No issue

✅ Correct Answer: B

💡 Explanation:

  • Interval is never cleared
  • Continues even after unmount

7. Which is TRUE about dependency comparison?

Options:

A. Deep comparison B. Reference comparison (Object.is) C. JSON stringify comparison D. Random comparison

✅ Correct Answer: B


8. What happens here?

useEffect(() => {
  console.log("run");
}, [{ a: 1 }]);

Options:

A. Runs once B. Never runs C. Runs every render D. Throws error

✅ Correct Answer: C

💡 Explanation:

  • New object created each render
  • Reference always different

9. What is the best fix?

useEffect(() => {
  doSomething(obj);
}, [obj]);
Where obj = { a: 1 } inside component.

Options:

A. Ignore it B. Move object outside component C. Memoize with useMemo D. Both B and C

✅ Correct Answer: D


10. What is logged?

useEffect(() => {
  console.log("A");

  return () => console.log("B");
}, [count]);
When count changes.

Options:

A. A B. B → A C. A → B D. B only

✅ Correct Answer: B

💡 Explanation:

  • Cleanup runs before next effect
  • Order: B (cleanup) → A (new effect)

11. What is wrong here?

useEffect(async () => {
  const data = await fetchData();
  setData(data);
}, []);

Options:

A. Nothing B. Async not allowed C. Cleanup breaks D. Both B and C

✅ Correct Answer: D


12. What is the best pattern for async?

Options:

A.
useEffect(async () => {})
B.
useEffect(() => {
  async function load() {}
  load();
}, []);
C.
useEffect(() => Promise.resolve())
D.
useEffect(() => {}, async [])

✅ Correct Answer: B


13. Which scenario causes stale closure?

Options:

A.
useEffect(() => {
  console.log(count);
}, [count]);
B.
useEffect(() => {
  console.log(count);
}, []);
C.
useEffect(() => {
  setCount(prev => prev + 1);
}, []);
D. None

✅ Correct Answer: B


14. When should you use useLayoutEffect instead of useEffect?

Options:

A. API calls B. Logging C. DOM measurement before paint D. Timers

✅ Correct Answer: C


15. What happens if dependency array is omitted?

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

Options:

A. Runs once B. Runs on every render C. Runs only on state change D. Never runs

✅ Correct Answer: B


16. What is the best alternative here?

useEffect(() => {
  setFullName(first + last);
}, [first, last]);

Options:

A. Keep as is B. Move to render calculation C. Use useMemo D. Remove dependencies

✅ Correct Answer: B

💡 Explanation:

  • Derived state → no need for effect

17. What is the issue here?

useEffect(() => {
  fetch(`/api/${id}`).then(res => setData(res));
}, [id]);

Options:

A. No issue B. Race condition risk C. Infinite loop D. Memory leak only

✅ Correct Answer: B


18. Which solution prevents race conditions?

Options:

A. useMemo B. AbortController C. useRef only D. setTimeout

✅ Correct Answer: B


Options:

A. Faster rendering B. Better separation of concerns C. Required by React D. Reduces bundle size

✅ Correct Answer: B


20. What is the conceptual model of useEffect?

Options:

A. Lifecycle replacement B. Event system C. Synchronization mechanism D. State manager

✅ Correct Answer: C


🚀 Final Note

These questions test whether a candidate:
  • Thinks in synchronization vs lifecycle
  • Understands React internals
  • Can handle real-world edge cases

🧠 Advanced useEffect — Real-World Coding Problems (Senior Level)


1. Data Fetch with Cancellation (Race Condition Handling)

📌 Problem

Build a component that fetches user data when userId changes. Ensure that older requests don’t overwrite newer ones.

Constraints

  • API latency is unpredictable
  • userId may change rapidly

Expected Behavior

  • Always show data for the latest userId
  • Prevent stale updates

Edge Cases

  • Fast switching between IDs
  • Component unmount during request

✅ Solution

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

  fetch(`/api/user/${userId}`, { signal: controller.signal })
    .then(res => res.json())
    .then(data => setUser(data))
    .catch(err => {
      if (err.name !== "AbortError") console.error(err);
    });

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

🧠 Explanation

  1. Each effect creates a new controller
  2. Cleanup aborts previous request
  3. Prevents stale response overwriting state

2. Debounced Search Input

📌 Problem

Implement a search input that triggers API calls only after user stops typing for 500ms.

Constraints

  • Avoid excessive API calls
  • Handle rapid typing

Expected Behavior

  • API called only after pause

✅ Solution

useEffect(() => {
  const timeout = setTimeout(() => {
    if (query) fetchResults(query);
  }, 500);

  return () => clearTimeout(timeout);
}, [query]);

🧠 Explanation

  • Timer resets on each keystroke
  • Only last input triggers API

3. Window Resize Listener Hook

📌 Problem

Track window width and update UI on resize.

Edge Cases

  • Component unmount
  • Multiple listeners

✅ Solution

useEffect(() => {
  const handleResize = () => setWidth(window.innerWidth);

  window.addEventListener("resize", handleResize);

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

🧠 Explanation

  • Cleanup prevents memory leaks
  • Stable listener ensures correctness

4. Interval Counter with Correct State

📌 Problem

Create a counter that increments every second.

Edge Case

  • Avoid stale closure

✅ Solution

useEffect(() => {
  const id = setInterval(() => {
    setCount(prev => prev + 1);
  }, 1000);

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

🧠 Explanation

  • Functional update avoids stale state

5. Sync Document Title with State

📌 Problem

Update page title whenever count changes.

✅ Solution

useEffect(() => {
  document.title = `Count: ${count}`;
}, [count]);

🧠 Explanation

  • Effect syncs external system (DOM)

6. Avoid Infinite Loop in Derived State

📌 Problem

Compute fullName from first and last.

❌ Bad Approach

useEffect(() => {
  setFullName(first + last);
}, [first, last]);

✅ Solution

const fullName = first + last;

🧠 Explanation

  • Derived state doesn’t need effect

7. API Polling System

📌 Problem

Poll an API every 5 seconds.

Constraints

  • Stop polling on unmount

✅ Solution

useEffect(() => {
  const id = setInterval(fetchData, 5000);
  return () => clearInterval(id);
}, []);

8. Conditional Fetch Based on Auth

📌 Problem

Fetch user data only if isLoggedIn is true.

✅ Solution

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

  fetchUser();
}, [isLoggedIn]);

9. Sync LocalStorage with State

📌 Problem

Persist theme preference.

✅ Solution

useEffect(() => {
  localStorage.setItem("theme", theme);
}, [theme]);

10. Initialize State from LocalStorage

📌 Problem

Load saved value on mount.

✅ Solution

useEffect(() => {
  const saved = localStorage.getItem("theme");
  if (saved) setTheme(saved);
}, []);

11. Avoid Function Dependency Trap

📌 Problem

Prevent effect re-run due to function reference.

✅ Solution

const fetchData = useCallback(() => {}, []);

useEffect(() => {
  fetchData();
}, [fetchData]);

12. Multiple Effects Separation

📌 Problem

Fetch data and update title.

✅ Solution

useEffect(fetchData, []);

useEffect(() => {
  document.title = "Dashboard";
}, []);

13. WebSocket Subscription

📌 Problem

Subscribe to a socket and clean up properly.

✅ Solution

useEffect(() => {
  socket.connect();

  return () => socket.disconnect();
}, []);

14. Prevent State Update After Unmount

📌 Problem

Avoid setting state after component unmount.

✅ Solution

useEffect(() => {
  let mounted = true;

  fetchData().then(data => {
    if (mounted) setData(data);
  });

  return () => {
    mounted = false;
  };
}, []);

15. Scroll Position Tracker

📌 Problem

Track scroll position.

✅ Solution

useEffect(() => {
  const handleScroll = () => setY(window.scrollY);

  window.addEventListener("scroll", handleScroll);

  return () => window.removeEventListener("scroll", handleScroll);
}, []);

16. Fetch with Retry Logic

📌 Problem

Retry API call up to 3 times.

✅ Solution

useEffect(() => {
  let attempts = 0;

  const fetchWithRetry = async () => {
    try {
      const res = await fetch("/api");
      setData(await res.json());
    } catch {
      if (attempts < 3) {
        attempts++;
        fetchWithRetry();
      }
    }
  };

  fetchWithRetry();
}, []);

17. Dynamic Event Listener Based on Prop

📌 Problem

Listen to different events based on prop.

✅ Solution

useEffect(() => {
  const handler = () => console.log("event");

  window.addEventListener(type, handler);

  return () => window.removeEventListener(type, handler);
}, [type]);

18. Avoid Multiple API Calls on Mount (Strict Mode)

📌 Problem

Prevent duplicate API calls in dev mode.

✅ Solution

useEffect(() => {
  let called = false;

  if (!called) {
    fetchData();
    called = true;
  }
}, []);

⚠️ Better approach:

  • Accept double call OR use external control (like caching)

19. Synchronize External Library

📌 Problem

Initialize third-party library once.

✅ Solution

useEffect(() => {
  const instance = initLibrary();

  return () => instance.destroy();
}, []);

🚀 Final Takeaway

These problems test:
  • Effect lifecycle understanding
  • Async control & cleanup
  • Dependency correctness
  • Real-world system thinking

🧠 useEffect Debugging Challenges (Senior Code Review Level)

These simulate real production bugs—not syntax errors, but subtle issues around behavior, performance, and correctness.

1. Stale Closure in Interval

❌ Buggy Code

useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1);
  }, 1000);

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

🔍 What’s wrong?

count is always stale (initial value).

💡 WHY it happens

  • Effect runs once
  • Closure captures initial count
  • Never updates

✅ Fix

useEffect(() => {
  const id = setInterval(() => {
    setCount(prev => prev + 1);
  }, 1000);

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

🧠 Best Practice

Use functional updates when state depends on previous value.

2. Missing Dependency → Stale Data

❌ Buggy Code

useEffect(() => {
  fetch(`/api/user/${userId}`)
    .then(res => res.json())
    .then(setUser);
}, []);

🔍 What’s wrong?

Does not refetch when userId changes.

💡 WHY

Dependency array is incorrect → effect never re-runs

✅ Fix

}, [userId]);

🧠 Best Practice

Always include all external values used inside effect.

3. Infinite Loop from State Update

❌ Buggy Code

useEffect(() => {
  setFiltered(data.filter(item => item.active));
}, [filtered]);

🔍 What’s wrong?

Infinite loop.

💡 WHY

  • Effect updates filtered
  • filtered is dependency → triggers effect again

✅ Fix

useEffect(() => {
  setFiltered(data.filter(item => item.active));
}, [data]);

🧠 Best Practice

Never depend on state you are setting inside the effect.

4. Function Dependency Causing Re-Renders

❌ Buggy Code

useEffect(() => {
  fetchData();
}, [fetchData]);

🔍 What’s wrong?

Effect runs every render.

💡 WHY

Function recreated each render → new reference

✅ Fix

const fetchData = useCallback(() => {}, []);

useEffect(() => {
  fetchData();
}, [fetchData]);

🧠 Best Practice

Stabilize function references with useCallback.

5. Object Dependency Trap

❌ Buggy Code

useEffect(() => {
  doSomething(options);
}, [options]);
Where:
const options = { sort: "asc" };

🔍 What’s wrong?

Effect runs every render.

💡 WHY

New object reference each render

✅ Fix

const options = useMemo(() => ({ sort: "asc" }), []);

🧠 Best Practice

Avoid inline objects/arrays in dependencies.

6. Memory Leak from Event Listener

❌ Buggy Code

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

🔍 What’s wrong?

Listener never removed.

💡 WHY

No cleanup → memory leak

✅ Fix

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

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

🧠 Best Practice

Always clean up subscriptions and listeners.

7. Async Directly in Effect

❌ Buggy Code

useEffect(async () => {
  const data = await fetchData();
  setData(data);
}, []);

🔍 What’s wrong?

Async function breaks effect contract.

💡 WHY

Effect expects cleanup function, not Promise

✅ Fix

useEffect(() => {
  async function load() {
    const data = await fetchData();
    setData(data);
  }
  load();
}, []);

8. Race Condition in Fetch

❌ Buggy Code

useEffect(() => {
  fetch(`/api/${id}`)
    .then(res => res.json())
    .then(setData);
}, [id]);

🔍 What’s wrong?

Older request may overwrite newer one.

💡 WHY

Async responses return out of order

✅ Fix

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

  fetch(`/api/${id}`, { signal: controller.signal })
    .then(res => res.json())
    .then(setData)
    .catch(err => {
      if (err.name !== "AbortError") console.error(err);
    });

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

🧠 Best Practice

Handle cancellation for async effects

9. Derived State Misuse

❌ Buggy Code

useEffect(() => {
  setFullName(first + last);
}, [first, last]);

🔍 What’s wrong?

Unnecessary state + effect

💡 WHY

Derived value doesn’t need effect

✅ Fix

const fullName = first + last;

🧠 Best Practice

Avoid effects for pure computations

10. Cleanup Runs Unexpectedly

❌ Buggy Code

useEffect(() => {
  console.log("Mount");

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

🔍 What’s wrong?

Cleanup runs on every update, not just unmount.

💡 WHY

Cleanup runs before next effect execution

✅ Fix

useEffect(() => {
  console.log("Mount");

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

🧠 Best Practice

Understand: cleanup runs on dependency change too

11. Strict Mode Double API Call

❌ Buggy Code

useEffect(() => {
  fetchData();
}, []);

🔍 What’s wrong?

API called twice in development.

💡 WHY

Strict Mode double-invokes effects

✅ Fix (acceptable pattern)

useEffect(() => {
  let ignore = false;

  fetchData().then(data => {
    if (!ignore) setData(data);
  });

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

🧠 Best Practice

Write effects that are idempotent

12. Missing Cleanup in Interval

❌ Buggy Code

useEffect(() => {
  setInterval(fetchData, 5000);
}, []);

🔍 What’s wrong?

Interval keeps running after unmount

✅ Fix

useEffect(() => {
  const id = setInterval(fetchData, 5000);
  return () => clearInterval(id);
}, []);

13. Conditional Hook Misuse

❌ Buggy Code

if (isLoggedIn) {
  useEffect(() => {
    fetchUser();
  }, []);
}

🔍 What’s wrong?

Hooks must not be conditional

💡 WHY

Breaks React hook ordering

✅ Fix

useEffect(() => {
  if (isLoggedIn) fetchUser();
}, [isLoggedIn]);

14. Updating State After Unmount

❌ Buggy Code

useEffect(() => {
  fetchData().then(setData);
}, []);

🔍 What’s wrong?

State update after unmount

💡 WHY

Async finishes after component removed

✅ Fix

useEffect(() => {
  let mounted = true;

  fetchData().then(data => {
    if (mounted) setData(data);
  });

  return () => {
    mounted = false;
  };
}, []);

15. Overloaded Effect

❌ Buggy Code

useEffect(() => {
  fetchData();
  document.title = "Dashboard";
  window.addEventListener("resize", handleResize);
}, []);

🔍 What’s wrong?

Multiple responsibilities

💡 WHY

Hard to maintain/debug

✅ Fix

useEffect(fetchData, []);

useEffect(() => {
  document.title = "Dashboard";
}, []);

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

16. Incorrect Dependency (Primitive vs Derived)

❌ Buggy Code

useEffect(() => {
  fetchData(user.id);
}, [user]);

🔍 What’s wrong?

Effect runs even if only unrelated user fields change

✅ Fix

}, [user.id]);

🧠 Best Practice

Depend on specific values, not whole objects

17. useEffect Used for Event Handling

❌ Buggy Code

useEffect(() => {
  if (clicked) {
    submitForm();
  }
}, [clicked]);

🔍 What’s wrong?

Effect used instead of event handler

💡 WHY

Unnecessary indirection

✅ Fix

const handleClick = () => {
  submitForm();
};

18. Flickering UI due to useEffect Timing

❌ Buggy Code

useEffect(() => {
  setHeight(ref.current.offsetHeight);
}, []);

🔍 What’s wrong?

UI flicker

💡 WHY

Effect runs after paint

✅ Fix

useLayoutEffect(() => {
  setHeight(ref.current.offsetHeight);
}, []);

🚀 Final Takeaway

These bugs test:
  • Mental model of effects
  • Dependency correctness
  • Async safety
  • Performance awareness

🧠 useEffect — Real-World Machine Coding Problems (Senior Architect Level)

These are production-grade problems focused on:
  • Side-effect management
  • State synchronization
  • Performance + correctness

1. Real-Time Search with Debounce + Cancellation

📌 Requirements

  • Input field for search
  • Fetch results from API after user stops typing (300–500ms)
  • Cancel previous requests if new query starts

🎯 UI Behavior

  • Loading spinner while fetching
  • Results update only for latest query

🔄 State / Data Flow

  • query → debounced value → API call → results

⚠️ Edge Cases

  • Rapid typing
  • Empty input
  • Slow network

⚡ Performance

  • Avoid unnecessary API calls
  • Avoid race conditions

🏗️ Suggested Architecture

  • useDebounce custom hook
  • useEffect for API call
  • AbortController for cancellation

🧩 Solution Approach

  1. Track query
  2. Debounce using setTimeout in useEffect
  3. Trigger API call on debounced value
  4. Cancel previous requests in cleanup

2. Infinite Scroll Feed (Like Social Media)

📌 Requirements

  • Load more posts when user scrolls near bottom
  • Append new data (pagination)

🎯 UI Behavior

  • Loader at bottom
  • No duplicate requests

🔄 Data Flow

  • page → API → append to posts

⚠️ Edge Cases

  • Fast scrolling
  • API failure
  • Duplicate triggers

⚡ Performance

  • Throttle scroll handler
  • Avoid multiple fetches

🏗️ Architecture

  • IntersectionObserver OR scroll listener
  • useEffect tied to page

🧩 Solution Approach

  1. Track page
  2. Trigger fetch when sentinel visible
  3. Append results
  4. Cleanup observer

3. Real-Time Notifications (Polling vs WebSocket)

📌 Requirements

  • Fetch notifications every 10 seconds OR use WebSocket

🎯 UI Behavior

  • Live updates
  • Badge count

🔄 Data Flow

  • Polling interval OR socket subscription → state

⚠️ Edge Cases

  • Tab inactive
  • Multiple subscriptions

⚡ Performance

  • Avoid redundant polling

🏗️ Architecture

  • useEffect with interval OR socket lifecycle

🧩 Solution Approach

  1. Start polling/socket in effect
  2. Cleanup on unmount
  3. Pause when tab inactive (optional)

4. Form Auto-Save (Draft Feature)

📌 Requirements

  • Save form data automatically every few seconds

🎯 UI Behavior

  • “Saving…” indicator
  • “Saved” confirmation

🔄 Data Flow

  • Form state → debounce → API

⚠️ Edge Cases

  • Rapid typing
  • Network failure

⚡ Performance

  • Avoid saving on every keystroke

🏗️ Architecture

  • Debounced useEffect
  • Retry logic

🧩 Solution Approach

  1. Watch form state
  2. Debounce save call
  3. Handle errors + retries

5. Theme Sync (System + LocalStorage + UI)

📌 Requirements

  • Sync theme between:
    • System preference
    • LocalStorage
    • UI

🎯 UI Behavior

  • Dark/light mode toggle

🔄 Data Flow

  • System → state → localStorage → DOM

⚠️ Edge Cases

  • System theme changes
  • First load

⚡ Performance

  • Avoid unnecessary re-renders

🏗️ Architecture

  • Multiple useEffects for each sync layer

🧩 Solution Approach

  1. Read localStorage on mount
  2. Listen to system changes
  3. Update DOM class

6. Live Chat System (WebSocket Lifecycle)

📌 Requirements

  • Connect to WebSocket
  • Receive/send messages

🎯 UI Behavior

  • Real-time chat updates

🔄 Data Flow

  • Socket → messages state

⚠️ Edge Cases

  • Reconnect on disconnect
  • Cleanup on unmount

⚡ Performance

  • Avoid multiple connections

🏗️ Architecture

  • useEffect for socket lifecycle

🧩 Solution Approach

  1. Connect in effect
  2. Add listeners
  3. Cleanup on unmount

7. Multi-Tab Sync (LocalStorage Event)

📌 Requirements

  • Sync state across browser tabs

🎯 UI Behavior

  • Changes in one tab reflect in others

🔄 Data Flow

  • localStorage → storage event → state

⚠️ Edge Cases

  • Same tab updates don’t trigger event

🏗️ Architecture

  • useEffect for storage listener

🧩 Solution Approach

  1. Listen to storage event
  2. Update state accordingly
  3. Cleanup listener

8. Dynamic Script Loader (3rd Party SDK)

📌 Requirements

  • Load external script (e.g. payment SDK)
  • Initialize only once

🎯 UI Behavior

  • Show loader until script ready

⚠️ Edge Cases

  • Script already loaded
  • Multiple mounts

⚡ Performance

  • Avoid duplicate script injection

🏗️ Architecture

  • Singleton loader + effect

🧩 Solution Approach

  1. Check if script exists
  2. Inject if not
  3. Cleanup if needed

9. Video Player Sync (Play/Pause State)

📌 Requirements

  • Sync React state with HTML video player

🎯 UI Behavior

  • Button controls video playback

🔄 Data Flow

  • state → DOM API (video.play())

⚠️ Edge Cases

  • User manually pauses video

🏗️ Architecture

  • useRef + useEffect

🧩 Solution Approach

  1. Store ref
  2. Effect reacts to isPlaying
  3. Call DOM APIs

10. Data Fetch with Cache Layer

📌 Requirements

  • Fetch data but cache results to avoid refetching

🎯 UI Behavior

  • Instant load for cached data

🔄 Data Flow

  • cache → state → API fallback

⚠️ Edge Cases

  • Stale cache

⚡ Performance

  • Minimize network calls

🏗️ Architecture

  • In-memory cache or context

🧩 Solution Approach

  1. Check cache first
  2. Fetch if missing
  3. Store result

11. Page Visibility-Based Polling

📌 Requirements

  • Pause polling when tab is inactive

🎯 UI Behavior

  • Resume when user returns

⚠️ Edge Cases

  • Frequent tab switching

🏗️ Architecture

  • visibilitychange listener

🧩 Solution Approach

  1. Listen to visibility API
  2. Start/stop polling accordingly

12. Scroll Restoration (SPA Navigation)

📌 Requirements

  • Restore scroll position on navigation

🎯 UI Behavior

  • Return to previous scroll position

🔄 Data Flow

  • route → scroll position map

⚠️ Edge Cases

  • Dynamic content height

🏗️ Architecture

  • useEffect + router integration

🧩 Solution Approach

  1. Save scroll before route change
  2. Restore after mount

13. Analytics Tracker (Page Views + Events)

📌 Requirements

  • Track page views + user actions

🎯 UI Behavior

  • Invisible tracking

🔄 Data Flow

  • route → analytics API

⚠️ Edge Cases

  • Duplicate tracking in Strict Mode

🏗️ Architecture

  • useEffect tied to route

🧩 Solution Approach

  1. Trigger tracking on route change
  2. Deduplicate events

🚀 Final Takeaway

These problems test:
  • Effect orchestration
  • Async + cleanup mastery
  • Performance optimization
  • Architectural thinking

🧠 Senior-Level Interview Questions — useEffect

These questions are designed to evaluate:
  • Deep mental models
  • Real-world problem solving
  • Trade-offs & debugging ability

1. When would you deliberately avoid useEffect even if side effects seem involved?

🔁 Follow-up

  • Can you give an example where removing useEffect improved performance?

✅ Strong Answer

  • Avoid when logic is derivable during render
  • Example:
const fullName = first + last;
instead of syncing via effect
  • Also avoid when logic belongs in:
    • Event handlers
    • Memoization (useMemo)
  • Effects should be for external synchronization only

❌ Weak Answer

“Whenever possible”
➡️ Fails because it lacks criteria and reasoning

2. Explain why useEffect is considered a synchronization mechanism, not a lifecycle hook

🔁 Follow-up

  • How does this mental model change how you write code?

✅ Strong Answer

  • Lifecycle thinking = “run on mount/update”
  • Effect thinking = “keep X in sync with Y”
  • Example:
useEffect(() => {
  document.title = count;
}, [count]);

❌ Weak Answer

“It replaces lifecycle methods”
➡️ Too shallow, no conceptual clarity

3. How would you debug an effect that runs more times than expected?

🔁 Follow-up

  • What tools or techniques would you use?

✅ Strong Answer

  • Check dependency array
  • Log dependency values
  • Look for unstable references (functions/objects)
  • Use React DevTools Profiler

❌ Weak Answer

“Add console.log”
➡️ Lacks structured debugging approach

4. Why do stale closures occur, and how do you systematically prevent them?

🔁 Follow-up

  • When is adding dependencies NOT the best solution?

✅ Strong Answer

  • Closure captures values at render time
  • Fix:
    • Add dependencies
    • Use functional updates
    • Use refs for mutable values

❌ Weak Answer

“Add it to dependency array”
➡️ Not always correct (can cause loops)

5. Design a robust data-fetching effect for a rapidly changing parameter (e.g., search)

🔁 Follow-up

  • How do you prevent race conditions?

✅ Strong Answer

  • Debounce input
  • Use AbortController
  • Track latest request

❌ Weak Answer

“Call API in useEffect”
➡️ Ignores real-world issues

6. Why is putting functions in dependency arrays tricky?

🔁 Follow-up

  • When would you NOT use useCallback?

✅ Strong Answer

  • Functions change reference each render
  • Causes unnecessary re-runs
  • Use useCallback only when necessary

❌ Weak Answer

“Because it re-renders”
➡️ Incorrect reasoning

7. How does React internally schedule effects?

🔁 Follow-up

  • What is the order of cleanup vs execution?

✅ Strong Answer

  • Effects run after commit phase
  • Order:
Cleanup (prev) → Effect (new)

❌ Weak Answer

“After render”
➡️ Incomplete

8. How would you prevent duplicate API calls in Strict Mode?

🔁 Follow-up

  • Should you “fix” this or accept it?

✅ Strong Answer

  • Make effects idempotent
  • Use guards or caching
  • Don’t rely on “run once” assumption

❌ Weak Answer

“Disable Strict Mode”
➡️ Bad practice

9. When would you use useLayoutEffect instead of useEffect?

🔁 Follow-up

  • What is the performance trade-off?

✅ Strong Answer

  • DOM measurement before paint
  • Blocking → affects performance

❌ Weak Answer

“When useEffect doesn’t work”
➡️ No understanding

10. How do you handle cleanup for async operations?

🔁 Follow-up

  • What happens if you don’t?

✅ Strong Answer

  • AbortController or flags
  • Prevent state updates after unmount

❌ Weak Answer

“Use try/catch”
➡️ Not relevant

11. How would you structure effects in a large component?

🔁 Follow-up

  • What’s the downside of a single large effect?

✅ Strong Answer

  • Split by responsibility
  • Easier dependency management

❌ Weak Answer

“Keep everything in one place”
➡️ Poor scalability

12. What are the trade-offs of heavy useEffect usage?

🔁 Follow-up

  • What alternatives would you consider?

✅ Strong Answer

  • Hard to reason about dependencies
  • Leads to bugs
  • Alternatives:
    • Derived state
    • Event handlers
    • Server-side logic

❌ Weak Answer

“It slows performance”
➡️ Vague

13. How would you debug a memory leak caused by useEffect?

🔁 Follow-up

  • What patterns commonly cause leaks?

✅ Strong Answer

  • Look for missing cleanup
  • Event listeners, intervals, subscriptions

❌ Weak Answer

“Check console errors”
➡️ Insufficient

14. How do dependency arrays impact performance?

🔁 Follow-up

  • What happens if you over-specify dependencies?

✅ Strong Answer

  • Too many dependencies → frequent re-runs
  • Too few → stale data

❌ Weak Answer

“They control when effect runs”
➡️ Too basic

15. Explain a scenario where useEffect introduces a bug instead of solving one

🔁 Follow-up

  • How would you refactor?

✅ Strong Answer

  • Derived state example
  • Leads to unnecessary renders + bugs

❌ Weak Answer

“When used incorrectly”
➡️ No concrete example

16. How would you design a custom hook that uses useEffect safely?

🔁 Follow-up

  • What responsibilities should it hide?

✅ Strong Answer

  • Encapsulate side effects
  • Expose clean API
  • Handle cleanup internally

❌ Weak Answer

“Just wrap useEffect”
➡️ Too shallow

17. What happens if dependencies include objects or arrays?

🔁 Follow-up

  • How do you stabilize them?

✅ Strong Answer

  • Reference changes → re-run
  • Use useMemo

❌ Weak Answer

“It works fine”
➡️ Incorrect

18. How would you handle polling with useEffect efficiently?

🔁 Follow-up

  • How do you pause polling?

✅ Strong Answer

  • setInterval + cleanup
  • Pause via visibility API

❌ Weak Answer

“Use setInterval”
➡️ Incomplete

19. How do you decide whether logic belongs in useEffect vs event handler?

🔁 Follow-up

  • Give a real-world example

✅ Strong Answer

  • Event-triggered → handler
  • State sync → effect

❌ Weak Answer

“Depends”
➡️ Not actionable

20. Explain how useEffect behaves in concurrent rendering

🔁 Follow-up

  • Why must effects be resilient?

✅ Strong Answer

  • Effects may run multiple times
  • Must be idempotent

❌ Weak Answer

“It works same”
➡️ Incorrect

🚀 Final Insight

A strong candidate:
  • Thinks in synchronization, not lifecycle
  • Avoids unnecessary effects
  • Handles async, cleanup, and dependencies correctly
  • Understands React internals and trade-offs