JavaScript Closures
This lesson explains JavaScript Closures in JavaScript with beginner-friendly examples, practical use cases, and clear best practices.
JavaScript Closures Overview
A JavaScript closure is created when an inner function remembers and
accesses variables from its outer function even after the outer function has
finished executing. Closures are one of the most powerful features of
JavaScript and are widely used in callbacks, event handlers, modules,
private variables, memoization, and asynchronous programming.
Every time a function is created, JavaScript forms a closure around its
surrounding lexical environment. Understanding closures is essential for
writing modern JavaScript applications using React, Angular, Vue, Node.js,
and other frameworks.
| Concept | Description | Common Usage |
| Closure | Inner function remembers outer variables. | Private state. |
| Lexical Scope | Functions access variables from parent scopes. | Nested functions. |
| Private Variables | Hide internal data. | Encapsulation. |
| Callback Functions | Maintain surrounding state. | Events and timers. |
| Module Pattern | Create reusable private modules. | Large applications. |
JavaScript Closure Example
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter());
console.log(counter());
console.log(counter());
The inner function remembers the count variable even after
createCounter() finishes execution. This behavior is known as a
closure.
Top 10 JavaScript Closure Examples
Closures are frequently used in modern JavaScript applications for state
management, callbacks, encapsulation, and reusable utility functions.
| # | Example | Syntax |
| 1 | Counter Function | function counter() { let c = 0; return function() { return ++c; }; } |
| 2 | Private Variable | function user() { let name = "John"; return function() { return name; }; } |
| 3 | Greeting Function | function greet(msg) { return function(name) { return msg + name; }; } |
| 4 | Tax Calculator | function tax(rate) { return function(price) { return price * rate; }; } |
| 5 | Event Handler | button.onclick = function() { console.log("Clicked"); }; |
| 6 | setTimeout Callback | setTimeout(function() { console.log("Done"); }, 1000); |
| 7 | Memoization | function cache() { const data = {}; return function() { }; } |
| 8 | Module Pattern | function module() { return { }; } |
| 9 | Array Callback | numbers.map(function(n) { return n * 2; }); |
| 10 | Configuration Factory | function config(env) { return function() { return env; }; } |
Top 10 Tricky Closure Examples (with Fix)
These are common closure edge cases that confuse many developers. Each row
shows a tricky pattern and the safer approach.
| # | Tricky Scenario | Problem Pattern | Fix Pattern | Expected Result |
| 1 | var in loop with async callback | for (var i = 0; i < 3; i++) setTimeout(() => console.log(i), 0) | for (let i = 0; i < 3; i++) ... | 0,1,2 instead of 3,3,3 |
| 2 | Shared closure state across calls | const fn = makeCounter(); fn(); fn(); used globally | const a = makeCounter(); const b = makeCounter(); | Independent counters per instance |
| 3 | Capturing mutable object reference | config.theme = "dark"; return () => config.theme | const theme = config.theme; return () => theme | Stable snapshot value in closure |
| 4 | Closure inside forEach uses outer reassigned variable | let role; users.forEach(u => { role = u.role; handlers.push(() => role); }) | handlers.push(() => u.role) | Each handler returns correct user role |
| 5 | Event listeners in loop capture wrong index | for (var i = 0; i < btns.length; i++) btns[i].onclick = () => console.log(i) | for (let i = 0; i < btns.length; i++) ... | Correct button index on click |
| 6 | Memory leak via large closed-over data | const big = hugeArray; return () => big.length | return () => hugeArray.length; hugeArray = null when unused | Lower retained memory |
| 7 | Stale closure in async function | let token = getToken(); setTimeout(() => callApi(token), 1000) | setTimeout(() => callApi(getToken()), 1000) | Uses latest token at execution time |
| 8 | Closure with accidental global mutation | count = count + 1 (missing declaration in factory) | let count = 0; return () => ++count | State remains private and predictable |
| 9 | Memoization key collision | cache[arg] = result for object args | const key = JSON.stringify(arg) or Map | Correct cache hits/misses |
| 10 | Debounce closure loses this | return function() { clearTimeout(t); t = setTimeout(fn, d); } | t = setTimeout(() => fn.apply(this, args), d) | Correct context and arguments preserved |
Best Practices
- Use closures to create private variables.
- Keep closure functions small and focused.
- Avoid storing unnecessary large objects inside closures.
- Use closures for reusable factory functions.
- Use closures with callbacks and event handlers.
- Document complex closure logic for better maintainability.
- Release unused references to improve memory usage.
- Use modern module patterns when organizing applications.
Common Mistakes
- Confusing closures with scope.
- Keeping unnecessary variables alive inside closures.
- Creating memory leaks by retaining unused objects.
- Overusing nested functions.
- Using closures when a simple function is sufficient.
- Not understanding lexical scope.
- Writing overly complex closure logic.
Key Takeaways
- A closure remembers variables from its outer scope.
- Closures work because of JavaScript's lexical scope.
- They are commonly used for private variables and callbacks.
- Closures power many JavaScript design patterns.
- Modern frameworks rely heavily on closures.
- Understanding closures is essential for advanced JavaScript development.
Pro Tip
If you understand lexical scope, you already understand most of how
closures work. A closure simply allows a function to remember variables
from the scope in which it was created, even after that scope has finished
executing.