The Last Piece

episode 05

#Closures

Illustration of two adventurers entering a theater

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:

  1. Every time a function finishes executing — either by reaching a return statement or the end of its body — its Execution Context is destroyed, along with the variables and values stored in it.

  2. Scope visibility is one-directional: inner scopes can access outer scopes, but not the other way around.

  3. 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:

Lie to me

Venetian mask

Run this code with me.

function createTreasure() {
  let coins = 1;
 
  function addCoin() {
    coins = coins + 1;
  }
 
  addCoin();
}
 
createTreasure();
Diagram of fake closure part 1
  1. JavaScript creates the Global Execution Context, and the Thread of Execution starts evaluating the code.
  2. The reserved word function is read, and the function block is stored as the value of the identifier createTreasure. In other words, createTreasure now points to a function definition (its code won’t run until it’s invoked with parenthesis ()).
Diagram of fake closure part 2
  1. JavaScript reaches the line createTreasure();. Since parentheses follow the identifier createTreasure, JavaScript invokes the function and creates a new Execution Context for this call.
  2. The Execution Context for createTreasure is pushed onto the Call Stack.
  3. The Thread of Execution begins evaluating createTreasure’s body. It declares coins and initializes it to 1 in Local Memory.
  4. Next, JavaScript reads function addCoin() { ... } and stores that inner function block as the value of the identifier addCoin (also in the Local Memory of createTreasure).

After this point I need you to trust me. Acording to what we have seen this is the current situation:

  1. At the moment the createTreasure Execution Context its still alive.
  2. Which means we can acces to its variables from an inner scope, right?

So it has all sense for the following sequence to happen. ​⬇️​

Diagram of fake closure part 3
  1. JavaScript reaches addCoin();. The identifier addCoin is searched and found in the Local Memory of createTreasure, 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 for addCoin and pushes it onto the Call Stack.

    The Thread of Execution temporarily stops evaluating createTreasure and enters the addCoin Execution Context to run its code.

  2. JavaScript evaluates the assigment coins = coins + 1.

    • It looks for the identifier coins in addCoin’s Local Memory, but it is not found.
    • Then it looks in the outer scope (the surrounding scope is createTreasure) and finds coins variable there with the current value 1.
    • The expression coins + 1 evaluates to 2.
    • JavaScript assigns the evaluated value 2 back into coins.
    • This updates the existing coins variable in createTreasure’s Local Memory (so now coins is 2).
  3. The Thread of Execution reaches the end of addCoin function body (there is no return statement), so it implicitly returns undefined. Afterwards the addCoin Execution Context is destroyed and popped from the Call Stack.

  4. Execution continues in createTreasure. It reaches the end of its function body, so its Execution Context is destroyed and popped from the Call Stack.

  5. The Thread of Execution resumes in the createTreasure Execution Context right after the line addCoin(); and reaches the end of its function body (there is no return statement), so it also implicitly returns undefined.

    • The createTreasure Execution Context is destroyed and popped from the Call Stack.
    • Since the Local Memory of createTreasure is destroyed, the variable coins is destroyed too.
  6. 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

A puzzle 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.

Clousure trick diagram part 1
  1. JavaScript creates the Global Execution Context, and the Thread of Execution starts evaluating the code.

  2. The identifier createTreasure is declared in Global Memory and assigned the function definition.

  3. JavaScript reaches the line const incrementTreasure = createTreasure();.

    The identifier incrementTreasure is declared but not yet initialized. JavaScript needs to evaluate the expression createTreasure() to determine it's value. Since parentheses follow createTreasure, JavaScript knows the function must be invoked, so it creates a new Execution Context for that function call.

  4. The Execution Context for createTreasure is pushed onto the Call Stack.

Clousure trick diagram part 2
  1. The Thread of Execution begins evaluating the body of the function createTreasure.
  2. The identifier coins is declared in Local Memory and initialized with the numeric value 1.
  3. The identifier addCoin is declared in Local Memory and assigned the function definition.
  4. The Thread of Execution reaches the return statement, and the function definition referenced by addCoin is returned to the calling Execution Context (in this case global) as a value.
Clousure trick diagram part 3
  1. Because a return statement terminates a function’s execution, the createTreasure Execution Context is destroyed and removed from the Call Stack.

  2. Execution continues in the Global Execution Context right after createTreasure();.

    The constant incrementTreasure is initialized with the returned function definition addCoin as its value.

Clousure trick diagram part 4
  1. JavaScript encounters the expression incrementTreasure(), creates a new Execution Context to run the function, and pushes it onto the Call Stack.
  2. The Thread of Execution starts running the body of the function referenced by incrementTreasure (which is addCoin).
  3. When JavaScript evaluates the expression coins = coins + 1, it looks for the identifier coins in the Local Memory of addCoin Executio 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(); // 4

It’s wild, right? How is coins still there?

Chess pieces

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

Treasure chest

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();
Diagram exposing closures part 1
  1. The Global Execution Context is created, and the Thread of Execution starts evaluating the global code.

  2. The identifier createTreasure is created in Global Memory and assigned the function definition.

  3. JavaScript reaches the line: const incrementTreasure = createTreasure();.

    The identifier incrementTreasure is declared but not yet initialized. JavaScript needs to evaluate the expression createTreasure() to determine it's value. Since parentheses follow createTreasure, JavaScript knows the function must be invoked, so it creates a new Execution Context for that function call.

  4. The Execution Context for createTreasure is pushed onto the Call Stack.

Diagram exposing closures part 2
  1. The Thread of Execution starts running the body of the function createTreasure.

  2. The variable coins is declared in Local Memory and initialized with the value 1.

  3. The identifier addCoin is declared in Local Memory and assigned the function definition.

    Mental model: At the moment the function addCoin is 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 variable coins with the value 1 — 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 which addCoin was defined. This Lexical Environment contains the binding for coins and an outer reference to the next environment. The chain formed by these outer references is known as the scope chain.

Diagram exposing closures part 3
  1. JavaScript reaches the return statement. The function referenced by addCoin is 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 of createTreasure().

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.

Diagram exposing closures part 4
  1. The createTreasure execution context it's popped from the Call Stack.

  2. Execution continues in the Global Execution Context.

    The identifier incrementTreasure is assigned the returned value (the addCoin function definition) and stored in Global Memory.

Diagram exposing closures part 5
  1. JavaScript reaches the line incrementTreasure() in the global code.

    Seeing the parentheses (), it creates a new Execution Context to run the function referenced by incrementTreasure.

  2. The Execution Context for incrementTreasure is pushed onto the Call Stack.

  3. The Thread of Execution begins running the body of the function incrementTreasure.

  4. When JavaScript evaluates the expression coins = coins + 1, it looks for the identifier coins in the Local Memory of addCoin Executio Context, but it doesn’t find it.

    It then follows the reference stored in the function’s [[scope]] property and finds coins in the referenced Lexical Environment.

Diagram exposing closures part 6
  1. The expression coins + 1 evaluates to 2, and this value is assigned back to the variable coins, updating the binding in the referenced Lexical Environment.
Diagram exposing closures part 7
  1. The function finishes executing, so the incrementTreasure Execution Context is popped from the Call Stack.

  2. 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 coins lives.

  • 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!

Finish line