Closure Function
Introduction
Closures are one of the most powerful features in JavaScript, but also one of the most misunderstood. If you've ever wondered:
- "How does a function remember variables after it's done executing?"
- "Why can callback functions access variables from outer functions?"
- "What's the secret behind private variables in JavaScript?"
...then you're in the right place! The answer is closures.
What You'll Learn
Don't worry—closures are not as scary as they sound! We'll build your understanding step by step:
- Scope - Where variables live and who can access them
- Nested Scopes - Functions inside functions
- Lexical Scope - How JavaScript finds variables
- Closures - The magical final piece!
Don't skip the Scope, Scopes Nesting, and Lexical Scope sections! These concepts build on each other like a staircase. Once you understand these basics, closures will suddenly click! 💡
The Simple Definition (We'll Prove This!)
A closure is a function that remembers variables from the place where it was created, even after that place is gone.
Sound confusing? By the end of this guide, it'll make perfect sense!
Step 1: Understanding Scope 📦
Think of scope as a boundary or container that determines where a variable can be accessed.
Real-World Analogy
Imagine your house has different rooms:
🏠 House
├── 🛏️ Bedroom (has a "diary" variable)
├── 🍳 Kitchen (has a "recipe" variable)
└── 🛁 Bathroom (has a "towel" variable)
- The
diarylives in the bedroom—you can't access it from the kitchen - Each room (scope) has its own variables
- Different rooms can have items with the same name without conflict
Scope in JavaScript
function foo() {
// This is foo's scope (like a room)
let count = 0;
console.log(count); // ✅ Can access count here (inside the room)
}
foo(); // Output: 0
console.log(count); // ❌ Error! count doesn't exist outside foo's scope
Visualization:
┌─────────────────────────┐
│ foo() function scope │
│ │
│ let count = 0; │ ← count lives here
│ console.log(count); │ ← can access count
│ │
└─────────────────────────┘
console.log(count); ← ❌ count not accessible here!
In JavaScript, a scope is created by:
- A function
function() { } - A code block
{ }(withletorconst)
Variables inside a scope are only accessible within that scope!
Scope Isolation (The Good News!)
Different scopes can have variables with the same name without conflicting:
function foo() {
let count = 0; // foo's count
console.log(count); // 0
}
function bar() {
let count = 100; // bar's count (completely different!)
console.log(count); // 100
}
foo(); // Output: 0
bar(); // Output: 100
Visualization:
┌─────────────────┐ ┌─────────────────┐
│ foo() scope │ │ bar() scope │
│ │ │ │
│ count = 0 │ │ count = 100 │
│ │ │ │
└─────────────────┘ └─────────────────┘
↑ ↑
Different variables with the same name!
They don't interfere with each other.
Scope = The area where a variable lives and can be accessed
- Variables inside a function = only accessible inside that function
- Different functions can have variables with the same name
- This prevents naming conflicts and keeps code organized
Step 2: Nested Scopes 🎯
Now let's put scopes inside scopes, like Russian nesting dolls!
Real-World Analogy
🏢 Building (Global Scope)
└── 🏠 Apartment (Outer Function)
└── 🛏️ Bedroom (Inner Function)
- From the bedroom, you can access:
- Bedroom items ✅
- Apartment items ✅
- Building items (lobby) ✅
- From the apartment, you can access:
- Apartment items ✅
- Building items ✅
- Bedroom items? ❌ (Can't access inner items from outside!)
Nested Scopes in JavaScript
function outerFunc() {
// Outer scope
let outerVar = "I am from outer!";
function innerFunc() {
// Inner scope
console.log(outerVar); // ✅ Can access outerVar!
}
innerFunc(); // Call the inner function
}
outerFunc(); // Output: "I am from outer!"
Visualization:
┌──────────────────────────────────────┐
│ outerFunc() scope │
│ │
│ outerVar = "I am from outer!" │
│ │
│ ┌────────────────────────────────┐ │
│ │ innerFunc() scope │ │
│ │ │ │
│ │ console.log(outerVar); │ │
│ │ ↑ Can see outerVar! │ │
│ │ │ │
│ └────────────────────────────────┘ │
│ │
└──────────────────────────────────────┘
The Golden Rules of Nested Scopes
✅ Inner can access outer:
function outer() {
let x = "outer value";
function inner() {
console.log(x); // ✅ Can access x from outer scope
}
inner();
}
outer(); // Output: "outer value"
❌ Outer cannot access inner:
function outer() {
function inner() {
let x = "inner value";
}
inner();
console.log(x); // ❌ Error! x is not defined (it's inside inner)
}
Multiple Levels of Nesting
You can nest scopes as deep as you want!
function level1() {
let var1 = "level 1";
function level2() {
let var2 = "level 2";
function level3() {
let var3 = "level 3";
// level3 can access all outer variables!
console.log(var1); // ✅ "level 1"
console.log(var2); // ✅ "level 2"
console.log(var3); // ✅ "level 3"
}
level3();
}
level2();
}
level1();
Visualization:
┌─────────────────────────────────────────────┐
│ level1() - can access: var1 │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ level2() - can access: var1, var2 │ │
│ │ │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ level3() - can access: │ │ │
│ │ │ var1, var2, var3 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────┘
Two important rules:
- ✅ Scopes can be nested (functions inside functions)
- ✅ Inner scopes can access outer scope variables
This is fundamental to understanding closures!
Lexical scope
How does JavaScript understand that outerVar inside innerFunc() corresponds to the variable outerVar of outerFunc()?
JavaScript implements a scoping mechanism named lexical scoping (or static scoping). Lexical scoping means that the accessibility of variables is determined by the position of the variables inside the nested scopes.
Simpler, the lexical scoping means that inside the inner scope you can access variables of outer scopes.
It's called lexical (or static) because the engine determines (at lexing time) the nesting of scopes just by looking at the JavaScript source code, without executing it.
The distilled idea of the lexical scope:
The lexical scope consists of outer scopes determined statically.
For example:
const myGlobal = 0;
function func() {
const myVar = 1;
console.log(myGlobal); // logs "0"
function innerOfFunc() {
const myInnerVar = 2;
console.log(myVar, myGlobal); // logs "1 0"
function innerOfInnerOfFunc() {
console.log(myInnerVar, myVar, myGlobal); // logs "2 1 0"
}
innerOfInnerOfFunc();
}
innerOfFunc();
}
func();
The lexical scope of innerOfInnerOfFunc() consists of scopes of innerOfFunc(), func() and global scope (the outermost scope). Within innerOfInnerOfFunc() you can access the lexical scope variables myInnerVar, myVar and myGlobal.
The lexical scope of innerFunc() consists of func() and global scope. Within innerOfFunc() you can access the lexical scope variables myVar and myGlobal.
Finally, the lexical scope of func() consists of only the global scope. Within func() you can access the lexical scope variable myGlobal.
Step 4: The Closure! 🎉
Now comes the magical part! Let's modify our previous example slightly:
The Magic Happens
Before (Normal behavior):
function outerFunc() {
let outerVar = "I am outside!";
function innerFunc() {
console.log(outerVar);
}
innerFunc(); // Called inside outerFunc - this is normal
}
outerFunc();
After (Closure magic!):
function outerFunc() {
let outerVar = "I am outside!";
function innerFunc() {
console.log(outerVar);
}
return innerFunc; // Return the function (don't call it yet!)
}
// Get the inner function
const myFunc = outerFunc();
// Now call it OUTSIDE outerFunc
myFunc(); // Output: "I am outside!" 😲
Wait... What Just Happened?! 🤯
Let's break this down step by step:
function outerFunc() {
let outerVar = "Secret message!";
function innerFunc() {
console.log(outerVar); // Uses outerVar from parent
}
return innerFunc; // Give back the inner function
}
const myFunc = outerFunc();
// At this point:
// - outerFunc has finished executing
// - Normally, outerVar should be gone/destroyed
// - But... 🎩✨
myFunc(); // Output: "Secret message!"
// innerFunc still remembers outerVar!
// This is a CLOSURE!
Visualization:
Step 1: Call outerFunc()
┌───────────────────────────┐
│ outerFunc() │
│ outerVar = "Secret!" │ ← Variable created
│ │
│ return innerFunc │ ← Return function
└───────────────────────────┘
Step 2: outerFunc finishes, but innerFunc "remembers" outerVar
┌───────────────────────────┐
│ myFunc = innerFunc │
│ │
│ Still has access to: │
│ outerVar = "Secret!" ✨ │ ← Captured/closed over!
└───────────────────────────┘
Step 3: Call myFunc() later
myFunc() → logs "Secret!" ← Still works! 🎉
The Official Definition
A closure is a function that remembers and accesses variables from its outer (lexical) scope, even after the outer function has finished executing.
Simpler version:
A closure is a function that "remembers" variables from where it was created, no matter where it's called later.
How to Identify a Closure 🔍
Look for "alien variables" inside a function - variables that aren't defined in that function:
function outer() {
let message = "Hello"; // ← Defined in outer
function inner() {
console.log(message); // ← "alien" variable in inner
// ↑
// Not defined here, but still accessible!
// This makes inner() a closure!
}
return inner;
}
If an inner function uses variables from an outer function, it's a closure!
Real-World Analogy 🎒
Think of a closure like a backpack:
When innerFunc is created inside outerFunc:
- It's like packing a backpack
- It stores references to variables it needs (outerVar)
- When you take innerFunc outside (return it)
- It brings its backpack (the variables) with it!
- No matter where you use innerFunc later
- It still has access to those variables in its backpack
The function "closes over" (captures/encloses) the variables from its outer scope. It "closes" them in, preventing them from being destroyed.
Think of it like sealing variables in a time capsule!
Now let's see why closures are incredibly useful with practical examples!
Closure examples
1. Event handler
Let's display how many times a button is clicked:
let countClicked = 0;
myButton.addEventListener("click", function handleClick() {
countClicked++;
myText.innerText = `You clicked ${countClicked} times`;
});
When the button is clicked, handleClick() is executed somewhere inside of the DOM code. The execution happens far from the place of the definition.
But being a closure, handleClick() captures countClicked from the lexical scope and updates it when a click happens. Even more, myText is captured too.
2. Callbacks
Capturing variables from the lexical scope is useful in callbacks.
A setTimeout() callback:
const message = "Hello, World!";
setTimeout(function callback() {
console.log(message); // logs "Hello, World!"
}, 1000);
The callback() is a closure because it captures the variable message.
An iterator function for forEach():
let countEven = 0;
const items = [1, 5, 100, 10];
items.forEach(function iterator(number) {
if (number % 2 === 0) {
countEven++;
}
});
countEven; // => 2
The iterator is a closure because it captures countEven variable.
3. Functional programming
Currying happens when a function returns another function until the arguments are fully supplied.
For example:
function multiply(a) {
return function executeMultiply(b) {
return a * b;
};
}
const double = multiply(2);
double(3); // => 6
double(5); // => 10
const triple = multiply(3);
triple(4); // => 12
multiply is a curried function that returns another function.
Currying, an important concept of functional programming, is also possible thanks to closures.
executeMultiply(b) is a closure that captures a from its lexical scope. When the closure is invoked, the captured variable a and the parameter b are used to calculate a * b.
Conclusion
Closures can be used to create private variables, maintain state between function calls, and implement modules in JavaScript.