Closures
A closure is a function that remembers the variables from the place where it was created. It sounds abstract, but you use closures every time you write a callback.
On this page · 5 min · Beginner
On this page
- Learning objectives
- Explanation
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Beginner
- Updated
- Oct 1, 2026
Learning objectives#
- Define a closure in plain language
- Create private state with a closure
- Build function factories
- Recognise closures in event handlers and React hooks
Explanation#
When a function is created inside another function, it keeps access to the outer function’s variables — even after the outer function has returned. That combination of a function and its remembered variables is a closure.
Closures let you hide data. Variables inside the outer function cannot be reached from outside; only the returned inner functions can read or change them.
Every callback you pass to setTimeout, addEventListener or Array.map is a closure over the variables in scope where you wrote it.
Code example: A counter with private state#
function createCounter() {
let count = 0; // private: not accessible from outside
return {
increment() { count++; return count; },
reset() { count = 0; },
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
counter.reset();
console.log(counter.increment()); // 1
console.log(counter.count); // undefined
1 2 1 undefined
Step-by-step explanation#
- createCounter() declares count and returns an object with two methods.
- After createCounter() returns, count would normally disappear — but increment and reset still reference it, so it is kept alive.
- Each call to increment() updates the same remembered count.
- counter.count is undefined: count is not a property, it is a captured variable only the methods can reach.
- Calling createCounter() again would create a brand-new, independent count.
Real-world use case#
Debounce functions, memoisation caches, module patterns and React hooks all rely on closures. A useState setter in an event handler works because the handler closes over component variables.
Common mistakes#
A callback captures the variables from when it was created. In React, an interval created once can keep reading an old state value; use the functional updater setCount(c => c + 1).
A long-lived closure (e.g. a global event listener) keeps every captured variable alive. Remove listeners you no longer need.
Best practices#
- Use closures to encapsulate state instead of globals
- Keep captured data small in long-lived callbacks
- Prefer let in loops so each iteration gets its own binding
- Clean up listeners and timers
Remember this#
A closure = a function + the variables it remembers.
Inner functions keep access to outer variables.
Used for private state and callbacks.
Practice exercise#
Your task: Write makeMultiplier(factor) that returns a function multiplying its argument by factor. Create double and triple.
Show solution
function makeMultiplier(factor) {
return n => n * factor;
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(5), triple(5)); // 10 15
Mini quiz#
What does this print?
function outer() {
let x = 1;
return () => ++x;
}
const f = outer();
f();
console.log(f());
Want more? Practise all JavaScript MCQs · JavaScript interview questions
My notes#
Saved in this browser.