JavaScript Closures
Understanding lexical scope and closure patterns in JavaScript
JavaScript Closures
In this document, I want to explain what a closure is, how it works in JavaScript, and why it is important in real-world applications such as React state management and module patterns.
Figure: Closure enables private state and controlled access.
1. Lexical Scope Basics
JavaScript follows lexical scoping:
- Variables are only accessible in the scope where they are declared.
- When code execution leaves that scope, the variables normally become inaccessible.
function foo() {
let secret = 42;
console.log(secret); // 42
}
foo();
console.log(secret); // β ReferenceError
π secret
exists only inside foo
. Outside access is impossible.
2. Variable Lifetime in Functions
- When a function executes, variables inside it are allocated in memory.
- Once the function returns, those variables are marked for garbage collection since they are no longer needed.
- This ensures memory efficiency, but prevents external access.
3. The Problem
Sometimes, we want a function to have private state that persists across calls.
Naive solution: use global variables.
- β Globals remain in memory for the entire program lifetime.
- β Easy to overwrite accidentally.
- β Breaks encapsulation.
let count = 0; // global
function increment() {
count++;
return count;
}
This works, but exposes count
to the entire application.
4. Closures: Functions Remember Their Scope
The solution is a closure. A closure is created when a function returns another function that references variables from its outer scope.
function createCounter() {
let count = 0; // hidden inside closure
return function () {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
How it works
createCounter
finishes execution, but the inner function still referencescount
.- The JavaScript engine keeps
count
alive in memory because it is captured by the closure. - Each call to
counter()
continues from the previous state.
π Closure = βA function remembers the scope in which it was created.β
5. Independent States
Each call to the closure-creating function generates a new lexical environment.
const counterA = createCounter();
const counterB = createCounter();
console.log(counterA()); // 1
console.log(counterA()); // 2
console.log(counterB()); // 1 (separate state)
π This allows independent states, similar to how multiple React components each maintain their own state.
Figure: Each closure instance maintains its own lexical environment.
6. React Example
Reactβs useState
hook relies on closure-like behavior.
function App() {
const [count, setCount] = useState(0);
// Internally: useState keeps "count" alive across re-renders
}
- React preserves state values using closures bound to component functions.
- This is why values persist between re-renders, even though functions re-execute.
Figure: Simplified view of how React stores state via closures across renders.
7. Encapsulation with Module Pattern
Closures can be used to hide internal state and expose only controlled accessors.
function createUser(name) {
let _name = name; // private
return {
getName: () => _name,
setName: (newName) => {
_name = newName;
},
};
}
const user = createUser("Alice");
console.log(user.getName()); // Alice
user.setName("Bob");
console.log(user.getName()); // Bob
console.log(user._name); // undefined (cannot access directly)
π This pattern achieves data encapsulation, similar to private fields in other languages.