JavaScript 17 🧬 closure
A closure is a function that retains access to its lexical scope, even when the function is executed outside of that scope. This means a closure can “remember” and access variables from the environment in which it was created — even after that environment has finished executing.
A Quick Look at the Examples
// 1. Basic closure — counter
function createCounter() {
let count = 0;
return function() {
count += 1;
console.log(count);
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
// 2. Closure with encapsulation — person
function createPerson(name) {
let age = 0; // Private variable
return {
getName: function() {
return name;
},
getAge: function() {
return age;
},
setAge: function(newAge) {
if (newAge >= 0) {
age = newAge;
}
}
};
}
const person = createPerson('John');
console.log(person.getName()); // "John"
console.log(person.getAge()); // 0
person.setAge(30);
console.log(person.getAge()); // 30
a. Closure Introduction
A closure is a function that retains access to its lexical scope, even when the function is executed outside of that scope.
This means a closure can “remember” and access variables from the environment in which it was created — even after that environment has finished executing.
Key Concepts
1. Lexical Scope
Lexical scope refers to the way variables are scoped according to their location in the code, rather than when they are called.
A function’s lexical environment is determined by where it was defined, not where it was executed.
function outer() {
let message = "Hello"; // Lexically scoped to outer
function inner() {
console.log(message); // inner "remembers" message
}
return inner;
}
const myFunc = outer();
myFunc(); // "Hello" — message is still accessible!
2. Execution Context
The execution context is the environment in which JavaScript code executes. It includes the variable and function declarations that are currently accessible.
3. Closure Creation
When a function is created inside another function, the inner function retains access to the variables of the outer function’s scope.
┌─────────────────────────────────────┐
│ outer() execution context │
│ ┌─────────────────────────────┐ │
│ │ let count = 0; │ │
│ │ │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ inner() closure │ │ │
│ │ │ → accesses count │ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
Common Misconception
⚠️ “Closures capture values, not references” — This is a common misunderstanding!
Closures capture variables (by reference), not values. Changes to the original variable after the closure is created do affect the closure.
function outer() {
let count = 0;
function increment() {
count++;
console.log(count);
}
function getCount() {
return count;
}
// Modify count from outside
count = 100;
return { increment, getCount };
}
const { increment, getCount } = outer();
console.log(getCount()); // 100 — not 0!
increment(); // 101 — closure sees the updated value
What closures actually capture: The variable binding (reference to the variable), not a snapshot of its value at creation time.
Conclusion
Closures enable encapsulation, data hiding, and state management — making them an essential tool for any JavaScript developer.
b. Closure Examples
Example 1: Counter (Basic Closure)
function createCounter() {
let count = 0;
return function() {
count += 1;
console.log(count);
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
Step-by-step:
| Step | Action |
|---|---|
| 1. Function Definition | createCounter is defined with a local variable count = 0 |
| 2. Inner Function | An anonymous function is defined inside and returned |
| 3. Closure Creation | When createCounter() is called, it returns the inner function |
| 4. Retained Access | The inner function retains access to count from its lexical scope |
| 5. Usage | Each call to counter() increments and logs the retained count |
Key insight: count persists between calls, even though createCounter has finished executing!
Example 2: Encapsulation and Data Hiding
function createPerson(name) {
let age = 0; // Private variable
return {
getName: function() {
return name;
},
getAge: function() {
return age;
},
setAge: function(newAge) {
if (newAge >= 0) {
age = newAge;
}
}
};
}
const person = createPerson('John');
console.log(person.getName()); // "John"
console.log(person.getAge()); // 0
person.setAge(30);
console.log(person.getAge()); // 30
person.setAge(-5); // Ignored — invalid
console.log(person.getAge()); // 30 (unchanged)
Key insight: age and name are private — not directly accessible from outside. They can only be read or modified through the returned methods.
console.log(person.age); // undefined — private!
console.log(person.name); // undefined — private!
Use Cases for Closures
| Use Case | Description | Example |
|---|---|---|
| Encapsulation & Data Hiding | Create private variables not accessible from outside | createPerson above |
| Factory Functions | Generate objects with specific behavior | createCounter, createPerson |
| Maintaining State in Async Operations | Retain access to variables across async calls | See example below |
| Event Handlers | Remember data specific to each handler | See example below |
| Partial Application / Currying | Create specialized functions from general ones | See example below |
Async Example:
function fetchData(url) {
let attempts = 0;
return function retry() {
attempts++;
console.log(`Attempt ${attempts} for ${url}`);
// fetch(url)... — attempts is retained across retries
};
}
const retryFetch = fetchData("https://api.example.com/data");
retryFetch(); // Attempt 1
retryFetch(); // Attempt 2
Event Handler Example:
function attachHandlers() {
for (let i = 1; i <= 3; i++) {
document.getElementById(`btn-${i}`).addEventListener('click', function() {
console.log(`Button ${i} clicked`); // Each closure remembers its own i
});
}
}
Partial Application Example:
function multiply(a) {
return function(b) {
return a * b;
};
}
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Complete Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Closures</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #f8f9fa;
color: #333;
line-height: 1.6;
}
h1 { color: #007bff; border-bottom: 3px solid #007bff; padding-bottom: 10px; }
h2 { color: #28a745; border-left: 4px solid #28a745; padding-left: 15px; margin-top: 30px; }
.demo-box {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin: 15px 0;
}
pre {
background: #1e1e1e;
color: #d4d4d4;
padding: 15px;
border-radius: 8px;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.8;
}
.keyword { color: #569cd6; }
.string { color: #ce9178; }
.number { color: #b5cea8; }
.function { color: #dcdcaa; }
.comment { color: #6a9955; }
.boolean { color: #569cd6; }
#output {
background: #e9ecef;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
min-height: 40px;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
border-left: 4px solid #007bff;
white-space: pre-wrap;
}
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
th, td {
padding: 10px;
border: 1px solid #ddd;
text-align: left;
}
th { background: #007bff; color: white; }
tr:nth-child(even) { background: #f8f9fa; }
.btn {
padding: 10px 20px;
background: #007bff;
color: white;
border: none;
border-radius: 6px;
font-size: 1em;
font-weight: bold;
cursor: pointer;
margin: 5px;
transition: all 0.3s;
}
.btn:hover {
background: #0056b3;
transform: translateY(-2px);
}
.btn-success { background: #28a745; }
.btn-success:hover { background: #1e7e34; }
.btn-danger { background: #dc3545; }
.btn-danger:hover { background: #a71d2a; }
.counter-display {
font-size: 3em;
font-weight: bold;
color: #007bff;
text-align: center;
padding: 20px;
font-family: 'Courier New', monospace;
}
</style>
</head>
<body>
<h1>Closures</h1>
<div class="demo-box">
<h2>1. Basic Closure — Counter</h2>
<pre>
<span class="keyword">function</span> <span class="function">createCounter</span>() {
<span class="keyword">let</span> count = <span class="number">0</span>;
<span class="keyword">return</span> <span class="keyword">function</span>() {
count += <span class="number">1</span>;
console.log(count);
};
}
<span class="keyword">const</span> counter = <span class="function">createCounter</span>();
counter(); <span class="comment">// 1</span>
counter(); <span class="comment">// 2</span>
counter(); <span class="comment">// 3</span>
</pre>
</div>
<div class="demo-box">
<h2>2. Interactive Counter</h2>
<p>Each click increments the private <code>count</code> variable — it persists between calls!</p>
<div class="counter-display" id="counterDisplay">0</div>
<button class="btn btn-success" onclick="incrementCounter()">+ Increment</button>
<button class="btn btn-danger" onclick="resetCounter()">Reset (new closure)</button>
</div>
<div class="demo-box">
<h2>3. Encapsulation — Private Variables</h2>
<pre>
<span class="keyword">function</span> <span class="function">createPerson</span>(name) {
<span class="keyword">let</span> age = <span class="number">0</span>; <span class="comment">// Private</span>
<span class="keyword">return</span> {
getName: <span class="keyword">function</span>() { <span class="keyword">return</span> name; },
getAge: <span class="keyword">function</span>() { <span class="keyword">return</span> age; },
setAge: <span class="keyword">function</span>(newAge) {
<span class="keyword">if</span> (newAge >= <span class="number">0</span>) age = newAge;
}
};
}
</pre>
</div>
<div class="demo-box">
<h2>4. Interactive Person</h2>
<div style="text-align: center; padding: 20px;">
<p>Name: <strong id="personName">—</strong></p>
<p>Age: <strong id="personAge">—</strong></p>
<button class="btn" onclick="changeAge(1)">+1 Year</button>
<button class="btn btn-success" onclick="changeAge(10)">+10 Years</button>
<button class="btn btn-danger" onclick="tryInvalidAge()">Try Invalid (-5)</button>
</div>
</div>
<div class="demo-box">
<h2>5. Closure Use Cases</h2>
<table>
<tr>
<th>Use Case</th>
<th>Description</th>
</tr>
<tr>
<td><strong>Data Hiding</strong></td>
<td>Private variables not accessible from outside</td>
</tr>
<tr>
<td><strong>Factory Functions</strong></td>
<td>Generate objects with specific behavior</td>
</tr>
<tr>
<td><strong>Partial Application</strong></td>
<td>Create specialized functions from general ones</td>
</tr>
<tr>
<td><strong>Event Handlers</strong></td>
<td>Remember data specific to each handler</td>
</tr>
<tr>
<td><strong>Async State</strong></td>
<td>Retain variables across asynchronous operations</td>
</tr>
</table>
</div>
<div class="demo-box">
<h2>6. Live Output — Closure Examples</h2>
<div id="output">Loading...</div>
</div>
<script>
// ============================================
// Closures — Live Demo
// ============================================
let results = [];
// 1. Basic counter
results.push('📌 Basic Counter Closure:\n');
function createCounter() {
let count = 0;
return function() {
count += 1;
return count;
};
}
const counter = createCounter();
results.push(' counter() → ' + counter());
results.push(' counter() → ' + counter());
results.push(' counter() → ' + counter());
results.push('');
// 2. Independent closures
results.push('📌 Independent Closures:\n');
const counterA = createCounter();
const counterB = createCounter();
results.push(' counterA() → ' + counterA()); // 1
results.push(' counterA() → ' + counterA()); // 2
results.push(' counterB() → ' + counterB()); // 1 (separate!)
results.push(' counterA() → ' + counterA()); // 3
results.push('');
// 3. Encapsulation
results.push('📌 Encapsulation — Private Variables:\n');
function createPerson(name) {
let age = 0;
return {
getName: function() { return name; },
getAge: function() { return age; },
setAge: function(newAge) {
if (newAge >= 0) age = newAge;
}
};
}
const person = createPerson('John');
results.push(' person.getName() → ' + person.getName());
results.push(' person.getAge() → ' + person.getAge());
person.setAge(30);
results.push(' After setAge(30): person.getAge() → ' + person.getAge());
person.setAge(-5);
results.push(' After setAge(-5): person.getAge() → ' + person.getAge() + ' (invalid ignored)');
results.push(' person.age → ' + person.age + ' (private — not accessible!)');
results.push('');
// 4. Partial application
results.push('📌 Partial Application (Currying):\n');
const multiply = a => b => a * b;
const double = multiply(2);
const triple = multiply(3);
results.push(' const double = multiply(2)');
results.push(' double(5) → ' + double(5));
results.push(' triple(5) → ' + triple(5));
results.push('');
// 5. Loop with let (block scope closure)
results.push('📌 Closures in Loops (with let):\n');
const functions = [];
for (let i = 1; i <= 3; i++) {
functions.push(() => i);
}
results.push(' for (let i = 1; i <= 3; i++) { functions.push(() => i); }');
results.push(' Each closure remembers its own i:');
functions.forEach((fn, idx) => {
results.push(' functions[' + idx + ']() → ' + fn());
});
results.push('');
// 6. Closures capturing references (not values)
results.push('📌 Closures Capture References (not values):\n');
function outer() {
let value = 10;
function show() {
return value;
}
value = 99; // Modify after closure creation
return show;
}
const showValue = outer();
results.push(' let value = 10; show() returns value; value = 99;');
results.push(' showValue() → ' + showValue() + ' (sees updated value!)');
results.push('');
// 7. Practical: Once function
results.push('📌 Practical: Once Function:\n');
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = fn(...args);
}
return result;
};
}
const initialize = once(() => "Initialized!");
results.push(' const initialize = once(() => "Initialized!");');
results.push(' initialize() → ' + initialize());
results.push(' initialize() → ' + initialize() + ' (only runs once)');
results.push('');
// 8. Practical: Memoization
results.push('📌 Practical: Memoization:\n');
function memoize(fn) {
const cache = {};
return function(n) {
if (n in cache) {
return cache[n] + ' (cached)';
}
const result = fn(n);
cache[n] = result;
return result + ' (computed)';
};
}
const slowSquare = memoize(n => n * n);
results.push(' square(5) → ' + slowSquare(5));
results.push(' square(5) → ' + slowSquare(5));
results.push(' square(6) → ' + slowSquare(6));
results.push(' square(6) → ' + slowSquare(6));
document.getElementById('output').textContent = results.join('\n');
// ============================================
// Interactive: Counter
// ============================================
let interactiveCounter = createCounter();
const counterDisplay = document.getElementById('counterDisplay');
function incrementCounter() {
const value = interactiveCounter();
counterDisplay.textContent = value;
counterDisplay.style.transform = 'scale(1.2)';
setTimeout(() => {
counterDisplay.style.transform = 'scale(1)';
}, 200);
}
function resetCounter() {
interactiveCounter = createCounter(); // New closure, fresh count
counterDisplay.textContent = '0';
}
// ============================================
// Interactive: Person
// ============================================
const interactivePerson = createPerson('Alice');
document.getElementById('personName').textContent = interactivePerson.getName();
document.getElementById('personAge').textContent = interactivePerson.getAge();
function changeAge(amount) {
const newAge = interactivePerson.getAge() + amount;
interactivePerson.setAge(newAge);
document.getElementById('personAge').textContent = interactivePerson.getAge();
}
function tryInvalidAge() {
const oldAge = interactivePerson.getAge();
interactivePerson.setAge(-5);
const newAge = interactivePerson.getAge();
if (oldAge === newAge) {
alert('❌ Invalid age rejected! Age stays at ' + newAge);
}
}
</script>
</body>
</html>
Quick Reference
What is a Closure?
| Aspect | Description |
|---|---|
| Definition | A function that retains access to its lexical scope |
| Created when | An inner function is defined inside an outer function |
| Retains | Access to outer function’s variables |
| Persists | Even after the outer function has finished executing |
Key Concepts
| Concept | Description |
|---|---|
| Lexical Scope | Variables scoped by location in code |
| Execution Context | Environment where code executes |
| Closure Creation | Inner function captures outer scope |
Common Misconception
| Misconception | Truth |
|---|---|
| “Closures capture values at creation time” | ❌ Closures capture variable bindings (by reference) |
| “Changes to the variable don’t affect the closure” | ❌ Changes do affect the closure |
Use Cases
| Use Case | Description |
|---|---|
| Data Hiding | Private variables |
| Factory Functions | Generate objects with behavior |
| Partial Application | Create specialized functions |
| Event Handlers | Remember per-handler data |
| Async State | Retain variables across async calls |
| Memoization | Cache function results |
| Once Functions | Run only once |
Best Practices
✅ Do This:
// Use closures for private state
function createCounter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count
};
}
// Use closures for factory functions
function createMultiplier(factor) {
return (num) => num * factor;
}
// Use closures for memoization
function memoize(fn) {
const cache = new Map();
return (n) => {
if (cache.has(n)) return cache.get(n);
const result = fn(n);
cache.set(n, result);
return result;
};
}
// Use let in loops to create per-iteration closures
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2 ✅
}
❌ Don’t Do This:
// Don't use var in loops with closures
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3 ❌
}
// Don't create closures in loops unnecessarily
for (let i = 0; i < 1000000; i++) {
functions.push(() => i); // Each closure holds memory
}
// Don't assume closures capture values
function outer() {
let x = 10;
return () => x;
}
const fn = outer();
// x is still accessible — but it's a reference, not a copy
// Don't forget closures hold memory
// (They can cause memory leaks if not cleaned up)
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
var in loops | All closures see final value | Use let |
| Memory leaks | Closures hold references | Null out references when done |
| Expecting value capture | Closures capture references | Be aware of reference semantics |
| Performance | Many closures = more memory | Use only when needed |
Closure Memory Model
┌──────────────────────────────────────┐
│ Global Scope │
│ │
│ ┌────────────────────────────────┐ │
│ │ createCounter() scope │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ let count = 0; │ │ │
│ │ │ (retained by closure) │ │ │
│ │ └──────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ return function() { │ │ │
│ │ │ count++; │ │ │
│ │ │ } │ │ │
│ │ └──────────────────────────┘ │ │
│ └────────────────────────────────┘ │
│ │
│ const counter = ← closure reference │
└──────────────────────────────────────┘
Pro Tip: Closures are one of JavaScript’s most powerful features. They enable encapsulation (private variables), factory functions, partial application, and state management in async operations. Remember: closures capture variable bindings (references), not values — so changes to the variable after creation do affect the closure. Always use let (not var) in loops with closures to get per-iteration bindings. And be mindful of memory — closures hold references to their outer scope, which can prevent garbage collection if not managed carefully!
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!