HT
How Things Work

Closures & Scope Chain

How functions 'remember' variables from their creation context β€” the foundation of data privacy, callbacks, and functional patterns in JavaScript.

How It Works

A closure is created every time a function is defined inside another function and references the outer function's variables. The inner function 'closes over' those variables, keeping them alive even after the outer function has returned.

1
Lexical Scoping

JavaScript uses lexical (static) scoping β€” a function's accessible variables are determined by WHERE it's written in the source code, not where it's called. Inner functions can access variables from all enclosing scopes.

2
The [[Scope]] Chain

When a function is created, the engine attaches a hidden [[Scope]] property linking to the parent scope's variable environment. This forms a chain: local scope β†’ parent scope β†’ … β†’ global scope.

3
Closure Formation

A closure occurs when a function references variables from an outer scope that has finished executing. Normally, local variables are garbage-collected when a function returns. But if an inner function still references them, the engine keeps those variables alive in a 'closure' object.

4
Variable Lookup

When a variable is accessed, the engine walks the scope chain: 1) Check local scope β†’ 2) Check enclosing scope β†’ 3) Keep walking up β†’ 4) Global scope β†’ 5) ReferenceError. This lookup happens EVERY time the variable is accessed.

Key Concepts

πŸ“¦Closure

A function bundled with references to its surrounding state (lexical environment). The closed-over variables persist even after the outer function returns.

πŸ—ΊοΈLexical Environment

An internal structure that holds variable bindings for a specific scope. Each function execution creates a new lexical environment.

πŸ”—Scope Chain

The linked list of lexical environments that the engine traverses when resolving variable references, from innermost to outermost scope.

⚠️var vs let/const

'var' is function-scoped (shared across loop iterations). 'let' and 'const' are block-scoped (each iteration gets its own binding).

Closure Factory Pattern
tsx
1// A closure is a function that "remembers"
2// variables from its creation scope:
3
4function createMultiplier(factor) {
5 // 'factor' is enclosed in the closure
6 return function (number) {
7 return number * factor;
8 };
9}
10
11const double = createMultiplier(2);
12const triple = createMultiplier(3);
13
14double(5); // 10 β€” uses factor=2
15triple(5); // 15 β€” uses factor=3
16
17// Each call to createMultiplier() creates
18// a NEW closure with its OWN 'factor'
πŸ’‘
Why This Matters

Closures power most JavaScript patterns: React hooks (useState, useEffect), Node.js middleware, event handlers, callbacks, currying, memoization, and the module pattern. Understanding closures is essential for debugging variable reference issues.

Common Pitfalls

⚠Memory leaks: Closures keep referenced variables alive. If a closure accidentally captures a large object (like a DOM node), it won't be garbage-collected even if no longer needed.
⚠Loop + var trap: Using 'var' in a for-loop creates ONE shared binding. All closures see the FINAL value. Use 'let' instead for per-iteration binding.
⚠Performance: Deep scope chains require more lookups. Accessing a variable from grandparent scope is slower than local access (though modern engines optimize this).
⚠Stale closures in React: useEffect callbacks close over state values at render time. Without the dependency array, callbacks may reference outdated values.
Real-World Use Cases

1Data Privacy with Module Pattern

Scenario

You need a counter module where 'count' cannot be directly modified from outside β€” only through increment/decrement methods. No class syntax, just functions.

Problem

Without closures, you'd store count as a global variable or property, making it accessible and modifiable by any code. This breaks encapsulation and makes bugs harder to track.

Solution

Use an IIFE (Immediately Invoked Function Expression) that returns an object with methods. The 'count' variable lives in the IIFE's closure, invisible to the outside world but accessible to the returned methods.

πŸ’‘

Takeaway: Closures are JavaScript's primary mechanism for data privacy. Before ES2022's #private fields, closures were the ONLY way to create truly private variables in JavaScript.

2The Classic Loop + setTimeout Bug

Scenario

You create 5 buttons in a loop, each supposed to alert its index when clicked. But every button alerts '5' β€” the final loop value.

Problem

Using 'var' in a for-loop creates a single shared binding. All 5 click handlers close over the SAME 'i' variable. By the time any handler runs, the loop has finished and i === 5.

Solution

Replace 'var' with 'let' (creates a new binding per iteration), or wrap the handler in an IIFE that captures the current value: (function(j) { button.onclick = () => alert(j); })(i). Both create a separate closure per iteration.

πŸ’‘

Takeaway: This is the most common closure bug in JavaScript. Understanding that 'var' is function-scoped (one binding) while 'let' is block-scoped (one binding per iteration) eliminates this entire class of bugs.