📦🔓Closures in JavaScript visualized
Tags: javascript, closures, functions, coding
Published: 2023-11-19 13:54:14

Have you ever experienced unexpected outcomes with JavaScript functions? Maybe it seemed like the return value was changing unexpectedly or magically 'remembering' previous values. Chances are, you were dealing with closures! 

Though closures may seem intimidating at first, they reveal extraordinary capabilities once you grasp them.

To understand how closures work behind the scenes, we first need to explore the execution context and the Environment Record. This article assumes you know the basics of [scope in JavaScript](https://developer.mozilla.org/en-US/docs/Glossary/Scope).

### The execution context

The execution context is a concept that describes the environment in which code is executed. It is defined by the [ECMAScript® Language Specification](https://262.ecma-international.org/13.0/#sec-execution-contexts)(the official specification for JavaScript) as: 

> An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation. At any point in time, there is at most one execution context per agent that is actually executing code. 

There are two main types of execution contexts: the **global execution context** and the **function execution context**. 

The global execution context represents the global scope and is created when the script runs. The function execution context, on the other hand, is created each time a function is invoked, and each function call has its own execution context.

When the script runs, the JavaScript engine establishes the global execution context. Subsequently, every time a function is invoked, an execution context specific to that function is created. Consequently, there can only be one global execution context while there can be multiple function execution contexts. At any point in time, there is only one running execution context. 

Explaining how execution contexts are managed is beyond the scope of this article, but you can learn more about it [here](https://www.geeksforgeeks.org/what-is-the-call-stack-in-javascript/) (I'll write an article about this soon!).

![execution contexts](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6kca2q65n3mw85a7kmln.png)

In addition to keeping track of code execution, the execution context plays a pivotal part in resolving variables and function values within its scope. This resolution mechanism is facilitated via one of its vital components, the Environment Record.

#### The Environment Record

During code execution, the JavaScript engine first establishes the rules for variable and function resolution before executing the code. It is during this phase that an Environment Record is created for each execution context. 

Similarly to the execution context, the Environment Record is an abstraction and cannot be directly accessed or manipulated within JavaScript code. It is defined by the [ECMAScript® Language Specification](https://262.ecma-international.org/13.0/#sec-environment-records) as: 

> Environment Record is a **specification type used to define the association of Identifiers to specific variables and functions, based upon the lexical nesting structure** of ECMAScript code. Usually an Environment Record is associated with some specific syntactic structure of ECMAScript code such as a FunctionDeclaration, a BlockStatement, or a Catch clause of a TryStatement. Each time such code is evaluated, a new Environment Record is created to record the identifier bindings that are created by that code.


> **Every Environment Record has an [[OuterEnv]] field, which is either null or a reference to an outer Environment Record**. This is used to model the logical nesting of Environment Record values.

Note that in previous editions, the ECMAScript® Language Specification used the term "lexical environment" [before it decided to rename it to "Environment Record"](https://github.com/tc39/ecma262/pull/1697) so you might encounter this term in other definitions and tutorials.

Think of the Environment Record as a container that holds variables and function values within a function declaration, block statement, or catch clause. It's important to note that block statements and catch clauses do not create new execution contexts on their own. The Environment Record associated with a block statement or catch clause is part of its outer execution context.

Each Environment Record can access its parent's Environment Record, forming a chain of access called the scope chain. The global scope, being the outermost scope, doesn't have a reference to a parent Environment Record — this is where the scope chain ends.

![scope chain](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sgib4e6a82iibdai50lf.png)

The JavaScript engine, when resolving variables and function calls, follows the Environment Record chain, checking each environment in the chain until it finds the needed variable or function, or until it reaches the global scope. This mechanism ensures access to variables and functions across different levels of nesting.

Let's see the execution context and Environment Record in action. Explore the live examples [here](https://codepen.io/audreykj/pen/gOqvWpq).

```javascript
const apple = '🍏'
const watermelon = '🍉'
const grapes = '🍇'

function makeFruitSalad() {
    const saladBowl = []
    saladBowl.push(apple, watermelon, grapes)

    return saladBowl
}

makeFruitSalad()
```

![example 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ic0mzs3r2tshz0c5jn11.png)

- As the script is executed, a global execution context is initiated, accompanied by the establishment of its corresponding Environment Record.  The variables `apple`, `watermelon`, and `grapes`, along with the function `makeFruitSalad` are referenced within this Environment Record because they are defined in the global scope.
- When `makeFruitSalad` is invoked, a function execution context is created along with its associated Environment Record. The Environment Record includes a reference to the variable `saladBowl` declared within the function's scope. It also references its outer Environment Record, which is the global Environment Record.

The crucial point is that the function `makeFruitSalad` has access not only to the variables declared within its scope but also to variables in its outer (enclosing) scope. This is possible through the creation of Environment Records during the execution of the code. 

Now that we've understood how the execution context and the Environment Record work, let's dive into closures.

### Closures

Think of closures as packages 📦

**A closure is the combination of a function bundled together with references to its surrounding state** ([MDN definition](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)) 

The mechanism of closure is what allows functions to access external variables or functions that were in scope when they were defined. Every function in JavaScript holds the potential to be a closure, as they all possess the capability to access variables and functions defined in their outer scope.

Let's go back to our previous example:

```javascript
const apple = '🍏'
const watermelon = '🍉'
const grapes = '🍇'

function makeFruitSalad() {
    const saladBowl = []
    saladBowl.push(apple, watermelon, grapes)

    return saladBowl
}

makeFruitSalad()
```

Yes, `makeFruitSalad` forms a closure 😎

The closure comprises the function `makeFruitSalad` along with its surrounding state, which includes the variables `saladBowl`, `apple`, `watermelon`, and `grapes`.

`makeFruitSalad`'s Environment Record stores the value for the `saladBowl`' variable. It also references its parent Environment Record (the global Environment Record), where the values of the global variables for `apple`, `watermelon`, and `grapes` are stored. 

![closure in makeFruitSalad](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qipwcizwlyeqztns7t2b.png)

In this case, the variables `apple`, `watermelon`, `grapes`, and the function `makeFruitSalad` are declared in the global scope so it seems obvious that the function can access these variables. Closures' amazing superpower is not immediately apparent.

Let's consider another example:

```javascript
function printNumber(number) {

    function print() {
        console.log(number);
    }

    return print;
}

const myPrint = printNumber(10);
myPrint();
```

This example is a common use case of closure: a function is nested inside an outer function and accesses a variable defined in the outer function.

The inner function `print` is defined within the outer function `printNumber`. The nested function `print` references a variable from its outer scope (`number`). 

![example 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qefhkrs8x5cj9qf0m5tr.png)

The function `print` holds a reference to its parent Environment Record, which is the Environment Record of the function `printNumber`. In turn, the function `printNumber` holds a reference to its parent Environment Record, which is the global environment. Since the global scope is the outermost scope, it doesn't have a reference to its parent Environment Record.

- When `printNumber(10)` is called, an Environment Record is created for the execution of `printNumber`, storing the value 10 for the variable `number`.
- The invocation returns the inner function `print`, which is assigned to the variable `myPrint`. At this point, `myPrint` holds a reference to the `print` function.
- Upon calling `myPrint()`, it executes the inner function `print`. The closure mechanism allows `print` to retain access to the `number` variable, even after the outer function `printNumber` has finished executing. Hence, the output is 10.

When a function is invoked, a new execution context is created, which includes an Environment Record. The Environment Record is responsible for associating identifiers with their values. In the case of closures, the inner function (in this case `print`) maintains a reference to its outer function's (in this case `printNumber`) Environment Record.

When the inner function is defined, it retains access to the variables and functions from its outer scope. This means that even after the outer function completes its execution, the inner function retains access to these outer variables and functions through the reference to the outer function's Environment Record.

Now consider this change: what value is logged this time?

```javascript
function printNumber(number) {

  function print() {
      console.log(number);
  }

  number = number*2

  return print;
}

const myPrint = printNumber(10);
myPrint();
```

`myPrint()` now logs 20 😲 

Here the variable number is multiplied by 2 before the `print` function is returned. Consequently, `printNumber`'s Environment Record now stores 20 as the value for the variable number. Upon invoking `myPrint()`, the JavaScript engine sequentially accesses its Environment Record and then its outer Environment Record to access the value of the `number` variable.

The crucial point here is that closures maintain a live reference to their Environment Record. They don't store copies but rather dynamically reference entities defined in their Environment Record. 

Consider another example: what do you expect `counter.increment()` to log each time?

```javascript
function createCounter() {
  let count = 0;

  function increment() {
    count++;
    console.log("Count:", count);
  }

  count += 10;

  return {
    increment: increment
  };
}

let counter = createCounter();
counter.increment();
counter.increment();
```

_counter.increment()_ logs 11 and then 12 🤯  


`createCounter` is a function that initializes a variable `count` with the value 0. Inside `createCounter`, there is a nested function that increments `count` and logs the updated value. The variable `count` is then incremented by 10. The function returns an object with a method `increment` that refers to the inner function `increment`.

![example 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihjods7w5jd2m8ko9kdb.png)

- `createCounter` is called, and the returned object (with the `increment` method) is assigned to the variable `counter`. At this point, the Environment Record for increment includes the variable `count`, which has been modified to 10 during the execution of `createCounter`.
- When `counter.increment()` is called, it executes the inner function `increment`. The `increment` function has access to the variable `count` from its outer Environment Record, so it increments `count` and logs the updated value (Count: 11). 
- Calling `counter.increment()` again continues to use the same outer Environment Record. It increments the count again, and the output will be "Count: 12". The closure mechanism ensures that the inner function retains access to the modified value of `count` between invocations. 

The key point here is that the inner function (increment) retains access to the variables of its outer Environment Record (createCounter). This is why it appears as if the function was "remembering" the previous return value 🔮

Consider this change:

```javascript
function createCounter() {
  let count = 0;

  function increment() {
    count++;
    console.log("Count:", count);
  }

  count += 10;

  return {
    increment: increment
  };
}

let counter = createCounter();
counter.increment();
counter.increment();
let counter2 = createCounter();
counter2.increment();
```

`counter2.increment()` logs 11 and not 13 🙉

Each invocation of `createCounter()` creates a new Environment Record with its own `count` variable. Each counter instance "closes over" its own Environment Record, preserving its state between function calls.

---

Closures in JavaScript are a powerful tool: they leverage lexical scoping to create functions that retain access to outer-scope data. Closures are particularly useful for creating functions with private state or control access to variables. 

It's important to note that closures can incur a performance cost. Given that each function utilizing a closure maintains its own Environment Record and references to its parent's Environment Record, the information held by closures must persist in memory until it is no longer referenced.

If closures felt confusing before, I hope they make more sense now. Feel free to reach out if you have any comments or questions! 😊

You can also find me on [Github](https://github.com/AudreyKj), [LinkedIn](https://www.linkedin.com/in/audreykadjar/), and [Instagram](https://www.instagram.com/audreykadjar/).