JavaScript - Closures & Scope
Overview
Estimated time: 30–40 minutes
Closures let functions remember variables from their defining scope. Use them to build private state and factories.
Learning Objectives
- Create closures that capture state.
- Avoid common pitfalls with loops and async callbacks.
Prerequisites
Explanation
A closure is a function that "remembers" the environment in which it was created. This means it can access variables from its outer (enclosing) scope even after that scope has finished executing. Closures are fundamental for data privacy, function factories, and callback patterns in JavaScript.
Syntax
function outer() {
let outerVar = 'I am outside!';
function inner() {
console.log(outerVar); // inner has access to outerVar
}
return inner;
}
const fn = outer();
fn(); // 'I am outside!'
Output:
I am outside!
Example 1: Counter with Private State
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const c1 = makeCounter();
console.log(c1()); // 1
console.log(c1()); // 2
const c2 = makeCounter();
console.log(c2()); // 1
Output:
1
2
1
Each call to makeCounter()
creates a new closure with its own count
variable.
Example 2: Function Factory
function makeMultiplier(factor) {
return function(x) {
return x * factor;
};
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Output:
10
15
Example 3: Closures in Loops
// Using let captures per-iteration binding
const fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns[0](), fns[1](), fns[2]()); // 0 1 2
Output:
0 1 2
If you use var
instead of let
, all functions would return 3 (the final value of i
).
Common Pitfalls
- Using
var
in loops captures a single binding; preferlet
or IIFEs. - Unintended retention of large data via closures can cause memory leaks; null references when done.
Summary
Closures are a powerful feature in JavaScript, enabling private state, function factories, and advanced callback patterns. Mastering closures is essential for writing robust, modular, and maintainable code.
Exercises
- Implement
once(fn)
that runs a function only the first time and returns the cached result on subsequent calls. - Implement a simple
memoize(fn)
for single-argument functions.