JS Guide
HomeQuestionsTopicsCompaniesResources
BookmarksSearch

Built for developers preparing for JavaScript, React & TypeScript interviews.

ResourcesQuestionsSupport
HomeQuestionsSearchProgress
HomeQuestionsreact
Prev

Learn the concept

Hooks

react
senior
hooks

Implement a basic version of useEffect from scratch.

hooks
useEffect
implementation
internals
side-effects
cleanup
dependencies
Quick Answer

A simplified useEffect stores effect callbacks and their dependency arrays in a module-level array. After each render, it compares current dependencies against previous ones using shallow equality and runs the effect callback (plus its cleanup) only when dependencies have changed.

Detailed Explanation

How useEffect Works Conceptually:

  1. After each render, React walks through the list of effects registered during that render.
  2. For each effect, it compares the current dependency array against the stored one from the previous render.
  3. If any dependency has changed (or there is no previous render), React runs the cleanup function from the previous effect, then runs the new effect callback.
  4. The effect's cleanup function and dependency array are stored for comparison on the next render.

Dependency Comparison:

React uses Object.is() to compare each element in the dependency array. This means:

  • Primitives (numbers, strings, booleans) compare by value
  • Objects and arrays compare by reference — a new object {} is always different from the previous {}
  • undefined deps (no array passed) means the effect runs on every render
  • Empty array [] means the effect runs only on mount (no deps to change)

How Real React Differs:

  • Effects are scheduled after paint, not run synchronously. React uses requestIdleCallback-like scheduling to avoid blocking the browser's paint cycle.
  • useLayoutEffect runs synchronously before paint — use it when you need to read layout and synchronously re-render (measuring DOM elements, preventing flicker).
  • Cleanup runs before the next effect execution, not immediately after the render. It also runs on unmount.
  • Effects are stored as a linked list on the Fiber node's updateQueue, tagged with flags indicating whether they need to run.
  • In Strict Mode (development), React intentionally runs effects twice to help find missing cleanup functions.

The Cleanup Pattern:

Cleanup functions prevent memory leaks and stale subscriptions. Common cleanup patterns:

  • clearInterval / clearTimeout for timers
  • controller.abort() for in-flight fetch requests
  • unsubscribe() for event listeners and subscriptions
  • Setting a cancelled flag for async operations

Code Examples

Simplified useEffect implementationJavaScript
const effectStore = []; // Stores { deps, cleanup } for each effect
let effectIndex = 0;

function useEffect(callback, deps) {
  const currentIndex = effectIndex;
  const prevEffect = effectStore[currentIndex];

  // Determine if deps have changed
  const hasChanged = !prevEffect || // First render: always run
    deps === undefined ||            // No deps array: run every render
    !shallowEqual(deps, prevEffect.deps); // Deps changed

  if (hasChanged) {
    // Schedule effect to run after render
    queueMicrotask(() => {
      // Run cleanup from previous effect first
      if (prevEffect?.cleanup) {
        prevEffect.cleanup();
      }
      // Run the new effect and store its cleanup
      const cleanup = callback();
      effectStore[currentIndex] = { deps, cleanup };
    });
  } else {
    // Deps unchanged — preserve existing effect (don't re-run)
    effectStore[currentIndex] = prevEffect;
  }

  effectIndex++;
}

// Shallow comparison for dependency arrays
function shallowEqual(a, b) {
  if (a === b) return true;
  if (!a || !b || a.length !== b.length) return false;
  return a.every((val, i) => Object.is(val, b[i]));
}

Real-World Applications

Use Cases

Subscription Management

Understanding useEffect internals helps write correct cleanup patterns for WebSocket connections, event listeners, and real-time data subscriptions.

Dependency Array Debugging

Knowing how shallow comparison works helps diagnose infinite effect loops caused by objects or arrays in the dependency array.

Mini Projects

Effect Dependency Visualizer

advanced

Build a tool that shows which dependencies changed between renders and why an effect re-ran.

useLayoutEffect vs useEffect Demo

intermediate

Create a visual comparison showing the timing difference between useEffect (after paint) and useLayoutEffect (before paint).

Industry Examples

Meta

React's useEffect implementation is one of the most complex parts of the reconciler, with over 2,000 lines handling scheduling, priority, batching, and Suspense integration.

Preact

Preact's useEffect is implemented in ~100 lines, proving the core concept is simple even though React's production version adds extensive optimization.

Resources

React Docs - useEffect Reference

docs

A Complete Guide to useEffect - Dan Abramov

article

Related Questions

Implement a basic version of useState from scratch.

senior
hooks

Explain the useState and useEffect hooks and their common patterns.

mid
hooks
Previous
Implement a basic version of useState from scratch.
Prev