The Last Piece
episode 05#Closures
Good evening, ladies and gentlemen, and welcome to the theater. Please take your seats — the performance will begin shortly.
Please silence all cell pho— Okay, enough of that.
We are not in a theater. We are on a journey — a journey to Closures. And this is the final stretch.
Be ready.
Right now I am about to lie you. In the next section, I’m going to present a simplified version of how something works in JavaScript. It will sound reasonable. It will even seem correct.
For now, I ask you to go along with it.
Not because it’s the whole truth — but because it’s a necessary step to reach it.
If at any point things feel confusing, that’s expected. Stay with me — by the end of this article, everything will fall into place.
Before we start
Before we move on, let’s pause for a moment.
Throughout this journey we’ve covered several concepts. Some of them were stepping stones toward more complex ideas, others were important on their own. For closures to make sense, we need to keep a few of those ideas clearly in mind.
Remember this:
Every time a function finishes executing — either by reaching a
returnstatement or the end of its body — its Execution Context is destroyed, along with the variables and values stored in it.Scope visibility is one-directional: inner scopes can access outer scopes, but not the other way around.
Functions are just values, which means they can be returned from other functions.
If any of these concepts feel unfamiliar, you may want to review the following episodes before moving on:
-
Basics of how JavaScript runs code.
Global Execution Context, Global Memory, Thread of Execution.
-
Basics of how functions are executed.
Execution Context, Local Memory, Call Stack.
-
Basic scope lesson.
Block scope, Function Scope, Global Scope.
Lie to me
Run this code with me.
function createTreasure() {
let coins = 1;
function addCoin() {
coins = coins + 1;
}
addCoin();
}
createTreasure();
- JavaScript creates the Global Execution Context, and the Thread of Execution starts evaluating the code.
- The reserved word
functionis read, and the function block is stored as the value of the identifiercreateTreasure. In other words,createTreasurenow points to a function definition (its code won’t run until it’s invoked with parenthesis()).
- JavaScript reaches the line
createTreasure();. Since parentheses follow the identifiercreateTreasure, JavaScript invokes the function and creates a new Execution Context for this call. - The Execution Context for
createTreasureis pushed onto the Call Stack. - The Thread of Execution begins evaluating createTreasure’s body. It declares
coinsand initializes it to1in Local Memory. - Next, JavaScript reads
function addCoin() { ... }and stores that inner function block as the value of the identifieraddCoin(also in the Local Memory ofcreateTreasure).
After this point I need you to trust me. Acording to what we have seen this is the current situation:
- At the moment the
createTreasureExecution Context its still alive.- Which means we can acces to its variables from an inner scope, right?
So it has all sense for the following sequence to happen. ⬇️
-
JavaScript reaches
addCoin();. The identifieraddCoinis searched and found in the Local Memory ofcreateTreasure, where it points to a function definition. Because the identifier is immediately followed by parentheses(), JavaScript invokes the function. To do so, it creates a new Execution Context foraddCoinand pushes it onto the Call Stack.The Thread of Execution temporarily stops evaluating
createTreasureand enters theaddCoinExecution Context to run its code. -
JavaScript evaluates the assigment
coins = coins + 1.- It looks for the identifier
coinsinaddCoin’s Local Memory, but it is not found. - Then it looks in the outer scope (the surrounding scope is
createTreasure) and findscoinsvariable there with the current value1. - The expression
coins + 1evaluates to2. - JavaScript assigns the evaluated value
2back intocoins. - This updates the existing
coinsvariable increateTreasure’s Local Memory (so nowcoinsis2).
- It looks for the identifier
-
The
Thread of Executionreaches the end ofaddCoinfunction body (there is noreturnstatement), so it implicitly returnsundefined. Afterwards theaddCoin Execution Contextis destroyed and popped from the Call Stack. -
Execution continues in
createTreasure. It reaches the end of its function body, so its Execution Context is destroyed and popped from the Call Stack. -
The Thread of Execution resumes in the
createTreasure Execution Contextright after the lineaddCoin();and reaches the end of its function body (there is noreturnstatement), so it also implicitly returnsundefined.- The
createTreasure Execution Contextis destroyed and popped from the Call Stack. - Since the Local Memory of
createTreasureis destroyed, the variablecoinsis destroyed too.
- The
-
Execution continues in the Global Execution Context right after
createTreasure();. There is no more code to evaluate, so the Global Execution Context is destroyed and the program ends.
If we call addCoin() twice, the value of coins is updated twice within the same execution of createTreasure().
Logging the result confirms that coins has increased from 1 to 3.
function createTreasure() {
let coins = 1;
function addCoin() {
coins = coins + 1;
}
addCoin();
addCoin();
console.log(coins); // 3
}
createTreasure();But why can JavaScript access the coins variable?
Is it because we are running addCoin inside the Execution Context of createTreasure?
Is that what grants access to coins?
Or...
Is it something else? Are we missing a piece of the puzzle?
Yes. We definitely are.
Missing piece
What we’ve just seen makes complete sense with everything we’ve learned so far. But there’s a problem. There’s a leak in this model.
Let’s expose it with a new example.
function createTreasure() {
let coins = 1;
function addCoin() {
coins = coins + 1;
}
return addCoin;
}
const incrementTreasure = createTreasure();
incrementTreasure();In this case, we run the function addCoin after the Execution Context of createTreasure has already been destroyed.
So what happens now?
When JavaScript executes incrementTreasure(), will it still be able to access the variable coins?
Or will it throw an error because coins no longer exists?
Let’s find out.
-
JavaScript creates the Global Execution Context, and the Thread of Execution starts evaluating the code.
-
The identifier
createTreasureis declared in Global Memory and assigned the function definition. -
JavaScript reaches the line
const incrementTreasure = createTreasure();.The identifier
incrementTreasureis declared but not yet initialized. JavaScript needs to evaluate the expressioncreateTreasure()to determine it's value. Since parentheses followcreateTreasure, JavaScript knows the function must be invoked, so it creates a new Execution Context for that function call. -
The Execution Context for
createTreasureis pushed onto the Call Stack.
- The Thread of Execution begins evaluating the body of the function
createTreasure. - The identifier
coinsis declared in Local Memory and initialized with the numeric value1. - The identifier
addCoinis declared in Local Memory and assigned the function definition. - The
Thread of Executionreaches thereturnstatement, and the function definition referenced byaddCoinis returned to the calling Execution Context (in this case global) as a value.
-
Because a
returnstatement terminates a function’s execution, thecreateTreasureExecution Context is destroyed and removed from the Call Stack. -
Execution continues in the Global Execution Context right after
createTreasure();.The constant
incrementTreasureis initialized with the returned function definitionaddCoinas its value.
- JavaScript encounters the expression
incrementTreasure(), creates a new Execution Context to run the function, and pushes it onto the Call Stack. - The Thread of Execution starts running the body of the function referenced by
incrementTreasure(which isaddCoin). - When JavaScript evaluates the expression
coins = coins + 1, it looks for the identifiercoinsin the Local Memory ofaddCoinExecutio Context, but it doesn’t find it.
What happens next? The createTreasure Execution Context was popped from the Call Stack in step 10. According to what we know so far, once an Execution Context is gone, we shouldn’t be able to access the variables that lived inside it.
This means we cannot search for coins in the createTreasure Execution Context like we did in the previous example, because this time the createTreasure Execution Context no longer exists at this point.
And yet, if we add a console.log inside the addCoin function and run the code, we’ll see that the variable coins increments every time incrementTreasure() is called.
Try it yourself:
function createTreasure() {
let coins = 1;
function addCoin() {
coins = coins + 1;
console.log(coins);
}
return addCoin;
}
const incrementTreasure = createTreasure();
incrementTreasure(); // 2
incrementTreasure(); // 3
incrementTreasure(); // 4It’s wild, right? How is coins still there?
Well, the game was over before it even began.
In JavaScript terms, this means that a function already knows which variables it can access before it is ever executed. And that is the key.
More specifically, at the exact moment a function is defined, a hidden internal property is stored along with the function definition.
This property is often referred to as [[scope]], and it holds a reference to the Lexical Environment in which the function was defined.
This is important: when an Execution Context is destroyed, its Lexical Environment is not immediately discarded if there is still a reference to it.
The function’s [[scope]] property is such a reference.
A quick note on terminology
Throughout this article, I use the terms Global Memory and Local Memory to describe where variables are stored while JavaScript runs our code.
These are mental-model names, not official JavaScript terms.
In the JavaScript specification, this concept is called a Lexical Environment. Each Execution Context has one, and it is the structure that holds variable bindings and references to outer scopes.
So when you read:
Global Memory → think Global Lexical Environment
Local Memory → think Function / Block Lexical Environment
I use “memory” here because it helps us visualize how JavaScript looks up identifiers step by step. Under the hood, it is always the Lexical Environment doing the work.
Applied to our example: when the function addCoin is defined, the variables that are in its lexical scope — such as coins — become part of the Lexical Environment referenced by addCoin’s [[scope]].
Later, when JavaScript executes incrementTreasure and cannot find coins in its Local Memory, it follows the reference stored in [[scope]] and finds coins there.
We need to see this visually in an example. It will all make sense in the next section.
The Truth
No more lies. No more puzzle pieces. Let’s re-run the previous example with the full picture — the whole truth, and nothing but the truth.
function createTreasure() {
let coins = 1;
function addCoin() {
coins = coins + 1;
}
return addCoin;
}
const incrementTreasure = createTreasure();
incrementTreasure();
-
The
Global Execution Contextis created, and theThread of Executionstarts evaluating the global code. -
The identifier
createTreasureis created inGlobal Memoryand assigned the function definition. -
JavaScript reaches the line:
const incrementTreasure = createTreasure();.The identifier
incrementTreasureis declared but not yet initialized. JavaScript needs to evaluate the expressioncreateTreasure()to determine it's value. Since parentheses followcreateTreasure, JavaScript knows the function must be invoked, so it creates a new Execution Context for that function call. -
The Execution Context for
createTreasureis pushed onto the Call Stack.
-
The Thread of Execution starts running the body of the function
createTreasure. -
The variable
coinsis declared in Local Memory and initialized with the value1. -
The identifier
addCoinis declared in Local Memory and assigned the function definition.Mental model: At the moment the function
addCoinis defined, a hidden property commonly referred to as[[scope]]is stored along with the function definition. You can think of this as a backpack that contains links to all the surrounding data available at that moment — in this case, the variablecoinswith the value1— as well as a link to the outer scope, which in turn links to its own outer scope, and so on.More precisely: The
[[scope]]property is an internal reference to the Lexical Environment in whichaddCoinwas defined. This Lexical Environment contains the binding forcoinsand an outer reference to the next environment. The chain formed by these outer references is known as the scope chain.
- JavaScript reaches the
returnstatement. The function referenced byaddCoinis returned to the calling Execution Context (global) as a value — together with its[[scope]]reference (the backpack), which points to the Lexical Environment where it was defined. This ends the execution ofcreateTreasure().
Believe it or not
The term backpack is often used to describe the
[[scope]]internal property. The analogy works well: the function’s definition carries all the surrounding data with it, just like a backpack.But remember — it’s just an analogy. In reality, no data is being carried or copied in the function definition. It's a reference to a Lexical Environment.
-
The
createTreasureexecution context it's popped from the Call Stack. -
Execution continues in the Global Execution Context.
The identifier
incrementTreasureis assigned the returned value (theaddCoinfunction definition) and stored in Global Memory.
-
JavaScript reaches the line
incrementTreasure()in the global code.Seeing the parentheses
(), it creates a new Execution Context to run the function referenced byincrementTreasure. -
The Execution Context for
incrementTreasureis pushed onto the Call Stack. -
The Thread of Execution begins running the body of the function
incrementTreasure. -
When JavaScript evaluates the expression
coins = coins + 1, it looks for the identifiercoinsin the Local Memory ofaddCoinExecutio Context, but it doesn’t find it.It then follows the reference stored in the function’s
[[scope]]property and findscoinsin the referenced Lexical Environment.
- The expression
coins + 1evaluates to2, and this value is assigned back to the variablecoins, updating the binding in the referenced Lexical Environment.
-
The function finishes executing, so the
incrementTreasureExecution Context is popped from the Call Stack. -
Execution resumes in the Global Execution Context right after
incrementTreasure();. Since there is no more code to run, the program ends.
At this point, we can finally say it clearly.
A closure is created when a function is defined, not when it is executed.
A closure is a function together with a reference to the Lexical Environment in which it was defined.
That’s it. No mystery.
Because of this reference:
-
The function can access variables from its outer scopes.
-
Even after those outer Execution Contexts have finished executing.
-
As long as the function itself is still reachable.
In our example, addCoin is a closure because:
-
It was defined inside createTreasure.
-
It references the Lexical Environment where
coinslives. -
And that reference is preserved through the function’s
[[scope]].
Congratulations — you did it. You’ve reached the end of the journey, and hopefully with a much clearer understanding of how closures work in JavaScript.
Now claim your honor and go through the finish line. We’re done here. Good job!
