What is Closure?
To truly understand closures in JavaScript, we first need to understand how JavaScript executes code under the hood.
Closures are not a separate feature, they are a natural result of how Execution Contexts and Lexical Environments work.
Let's break this down step by step.
Execution Context
Whenever a function is invoked, JavaScript creates a new Execution Context for that function.
An Execution Contexts fundamentally consists of two main components:
- Variable Environment (Memory Component)
- Lexical Environment (Scope Resolution Component)
1. Variable Environment (Memory Component)
This is where the function's local data is stored.
Before a single line of code is executed, the JavaScript engine scans the function and allocates memory for:
-
Variables declared using
var,let, andconst - Function declarations (entire function definitions)
- Arguments object, which contains all values passed to the function
This setup happens during the Creation Phase of the Execution Context.
2. Lexical Environment (Scope Resolution Component)
This component is responsible for executing the code line by line and resolving variables.
It contains:
Scope Chain
A reference to the parent's Lexical Environment. This is how JavaScript looks up variables that are not present in the current scope.thisbinding
The value ofthis, determined by how the function is called.
The Two Phases of Execution Context
Every Execution Context is created in two phases:
1️⃣ Creation Phase
- Memory is allocated
- Variables are hoisted
- Functions are stored entirely
-
letandconstexist in the Temporal Dead Zone
2️⃣ Execution Phase
- Code runs line by line
- Variables receive their actual values
Let's Understand This with an Example
Step 1: Global Execution Context (GEC)
Before any code runs, JavaScript creates the Global Execution Context.
Memory Phase
-
outer→ stored as a function -
fn→ initialized asundefined
Execution Phase
- The engine reaches
const fn = outer() - This triggers the creation of a new Execution Context for
outer()
Step 2: outer() Execution Context
When outer() is called, its Execution Context is pushed onto the Call Stack.
Memory Phase
-
countis allocated (in TDZ because it’s declared with let) -
innerfunction is created and stored entirely
Execution Phase
-
countis assigned the value0 - The
innerfunction is returned
Termination
- The
outer()Execution Context is popped off the Call Stack
⚠️ Crucial Detail (This is the key to Closure)
Even though the outer() Execution Context is destroyed, its Lexical Environment is NOT garbage collected.
Why?
Because the returned function inner still holds a reference to it.
👉 This preserved reference is what we call a Closure.
Step 3: fn() Execution Context (First Call)
At this point:
const fn = outer();
fn now holds a reference to the inner function along with its hidden [[Environment]] link to outer’s Lexical Environment.
When we call:
fn();
Memory Phase
- No local variables
- Empty arguments object
Execution Phase
-
count++is executed - JavaScript looks for
countinfn’s local scope → not found - It follows the Scope Chain to
outer’s Lexical Environment -
countis found with value0 - It is incremented to
1 -
1is returned
So, What is a Closure?
A Closure is formed when a function "remembers" the variables from its lexical scope even after that scope's execution context has finished.
Closures happen because:
- Functions carry a hidden reference (
[[Environment]]) - This reference points to the Lexical Environment where the function was created
- As long as the function exists, that environment stays alive
Mental Model 🧠
🔑 Closure is not about function execution, it's about function creation.
A function remembers the lexical environment where it was defined, even after that environment's execution context is gone.




Top comments (0)