🔒 Closures
jsintermediate

🔒Closures

A closure is created when a function remembers and accesses variables from its outer (lexical) scope, even after the outer function has finished execution.

Memory Trick

Think of a closure as a function with a backpack. The backpack carries all the variables it needs from where it was born.

Log in to track your mastery & progress

1️⃣ Definition (WHAT)

A closure is created when a function remembers and can access variables from its outer (lexical) scope, even after the outer function has finished execution.

In simple terms: a function "closes over" the variables it needs.

2️⃣ Why it exists (WHY)

Closures exist to:

  • Preserve state across function calls
  • Encapsulate data (create private variables)
  • Enable callbacks and async operations to access outer variables

Without closures, JavaScript would struggle with:

  • Async code (timers, promises, event handlers)
  • Modular, reusable logic
  • Data privacy

3️⃣ How it works (HOW)

  • JavaScript uses lexical scoping
  • When a function is defined, it forms a scope chain
  • If an inner function references variables from its outer scope, JavaScript keeps those variables in memory
  • The inner function carries a reference to this scope → closure

Key point: Closures store references, not copies.

js
function outer() { let x = 10; return function inner() { return x; // closure over x }; } const fn = outer(); fn(); // 10 (outer() already finished!)

4️⃣ Real-world use cases (WHERE)

  • Debounce / throttle implementations — timer variable lives in outer function
  • Event handlers — access to outer state
  • Callbacks in async code — preserve context
  • Module pattern — private variables
  • React hooks — useState/useEffect capture outer scope
js
function makeCounter() { let count = 0; return { increment: () => ++count, decrement: () => --count, value: () => count, }; }

5️⃣ Trade-offs / Limitations

Closures are powerful, but:

  • ❌ Can cause memory leaks if references are not cleaned
  • ❌ Harder to debug when overused
  • ❌ Long-lived closures can retain large objects in memory
  • ❌ Can lead to unexpected behavior if variables are mutated

Especially risky with event listeners, intervals, and global closures.

6️⃣ Closures in Loops — Classic Trap

Closures capture variables, not values.

js
// With var — BUG for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Output: 3, 3, 3 (shared i ends at 3) // With let — FIXED for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Output: 0, 1, 2 (fresh binding per iteration)

let creates a new binding per iteration, var does not.

7️⃣ Memory Leak Example

js
function attachHandler() { const bigData = new Array(1_000_000); button.addEventListener('click', () => { console.log('clicked'); // bigData captured but unused! }); }

Even though bigData isn't used, it stays in memory because the closure captures the whole scope.

Fix: Nullify unused references or remove event listeners in cleanup.

Was this helpful?

Test your Knowledge

4 questions to cement what you just learned.