DevPrep
  • Interview Prep
  • Projects
  • Resources
  • Pricing
  • About Us
Submit Question
DevPrep
  • Pricing
  • About Us
Submit Question
  1. Home
  2. Articles
  3. Frontend Engineering
  4. Closures in JavaScript — What They Are and Why They Matter in Production
XLinkedInReddit
MediumFrontend Engineering

Closures in JavaScript — What They Are and Why They Matter in Production

D
DevPrep Team
February 9, 2026·4 min read·0
Table of Contents
  • What is a Closure?
  • Why Closures Exist — The Technical Reason
  • Real Production Use Cases
  • 1. React Hooks — Closures Everywhere
  • 2. Private Variables (Module Pattern)
  • 3. Function Factories
  • 4. Memoization / Caching
  • The Classic Pitfall — Loop + Closure
  • Production Issues I've Encountered
  • Issue 1: Memory Leak from Closures
  • Issue 2: Stale Closures in React
  • Best Practices
  • Interview Tip

Written by Rahul · Frontend Engineer at Google · Updated 2025

Closures are one of those concepts where once it clicks, you'll see them everywhere. I use closures every single day at Google — in React hooks, in utility functions, in caching layers. Let me explain it the way that finally made it click for me.

What is a Closure?

A closure is a function that remembers the variables from the scope where it was created, even after that scope has finished executing.

function createCounter() {
  let count = 0; // This variable is "enclosed"

  return function increment() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// "count" should be dead (createCounter finished executing)
// But the returned function still has access to it — that's a closure!

Why Closures Exist — The Technical Reason

When a function is created in JavaScript, it gets a hidden property called [[Environment]] that links to the scope where it was born. The garbage collector won't clean up that scope as long as the function exists.

Real Production Use Cases

1. React Hooks — Closures Everywhere

Every single React hook is powered by closures:

function SearchComponent() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);

  // This callback "closes over" query
  const handleSearch = useCallback(() => {
    fetch(`/api/search?q=${query}`)
      .then(res => res.json())
      .then(data => setResults(data));
  }, [query]); // query is a closed-over variable

  // The effect closes over handleSearch
  useEffect(() => {
    const timer = setTimeout(handleSearch, 300);
    return () => clearTimeout(timer);
  }, [handleSearch]);
}

2. Private Variables (Module Pattern)

function createAuthService() {
  let token = null; // Private — can't be accessed directly

  return {
    login(credentials) {
      token = authenticate(credentials);
    },
    getToken() {
      return token;
    },
    isAuthenticated() {
      return token !== null;
    },
    logout() {
      token = null;
    }
  };
}

const auth = createAuthService();
auth.login({ user: "rahul", pass: "***" });
console.log(auth.isAuthenticated()); // true
console.log(auth.token); // undefined — truly private!

3. Function Factories

function createMultiplier(factor) {
  return (number) => number * factor;
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
const toPercentage = createMultiplier(100);

console.log(double(5));       // 10
console.log(triple(5));       // 15
console.log(toPercentage(0.85)); // 85

4. Memoization / Caching

function memoize(fn) {
  const cache = new Map(); // Closed over by the returned function

  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      console.log("Cache hit!");
      return cache.get(key);
    }
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

const expensiveCalc = memoize((n) => {
  console.log("Computing...");
  return n * n;
});

expensiveCalc(5); // "Computing..." → 25
expensiveCalc(5); // "Cache hit!" → 25

The Classic Pitfall — Loop + Closure

// ❌ Classic bug
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Prints: 3, 3, 3 (not 0, 1, 2!)

// Why? All three functions close over the SAME "i" variable
// By the time setTimeout fires, i is already 3

// ✅ Fix 1: Use let (block scoping)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Prints: 0, 1, 2

// ✅ Fix 2: IIFE (creates new scope per iteration)
for (var i = 0; i < 3; i++) {
  ((j) => {
    setTimeout(() => console.log(j), 100);
  })(i);
}

Production Issues I've Encountered

Issue 1: Memory Leak from Closures

function setupHandler() {
  const hugeData = new Array(1000000).fill("x"); // 1M items

  document.addEventListener("click", () => {
    // This closure keeps hugeData alive forever!
    console.log(hugeData.length);
  });
}

// ✅ Fix: Only close over what you need
function setupHandlerFixed() {
  const hugeData = new Array(1000000).fill("x");
  const length = hugeData.length; // Extract what you need

  document.addEventListener("click", () => {
    console.log(length); // Only "length" is closed over, hugeData can be GC'd
  });
}

Issue 2: Stale Closures in React

function ChatComponent() {
  const [message, setMessage] = useState("");

  useEffect(() => {
    const interval = setInterval(() => {
      // ❌ "message" is stale — always the initial value ""
      console.log("Current message:", message);
    }, 1000);
    return () => clearInterval(interval);
  }, []); // Empty deps = closure captures initial message

  // ✅ Fix: Add message to dependency array
  // or use a ref for latest value
}

Best Practices

  1. Use let instead of var in loops — eliminates the classic closure bug
  2. Be mindful of what you close over — large objects in closures prevent garbage collection
  3. In React, watch your dependency arrays — stale closures are the #1 hooks bug
  4. Use closures for encapsulation — it's the JavaScript way to create private state
  5. Name your closures — for better stack traces in debugging

Interview Tip

When asked about closures, give the definition, then immediately show the loop example. Then explain a practical use case (memoization or module pattern). Bonus points if you mention stale closures in React hooks. That shows real-world experience.

Related Articles

MediumFrontend Engineering

System Design #12: Design a Multi-Step Form Wizard

7 min read
MediumFrontend Engineering

Mastering Senior-Level JavaScript Interview Concepts

2 min read
MediumFrontend Engineering

System Design #9: Design a Collaborative Text Editor

9 min read

Comments (0)

Sign in to leave a comment.

No comments yet. Be the first to comment.

Table of Contents

  • What is a Closure?
  • Why Closures Exist — The Technical Reason
  • Real Production Use Cases
  • 1. React Hooks — Closures Everywhere
  • 2. Private Variables (Module Pattern)
  • 3. Function Factories
  • 4. Memoization / Caching
  • The Classic Pitfall — Loop + Closure
  • Production Issues I've Encountered
  • Issue 1: Memory Leak from Closures
  • Issue 2: Stale Closures in React
  • Best Practices
  • Interview Tip

Series

View all Frontend Engineering articles →

Practice

  • JavaScript
  • DSA
  • Machine Coding
  • System Design

Resources

  • Learning Tracks
  • Articles
  • Roadmaps
  • Compare Concepts
  • Glossary
  • Developer Tools
  • All Questions

Company

  • About
  • Pricing

Legal

  • Privacy Policy
  • Terms of Service
DevPrep

© 2026 DevPrep. All rights reserved.