Mastering the Mysteries of JavaScript Scopes
Tags: javascript, programming, webdev, scope
Published: 2023-02-22 07:28:41

While writing your programs in JavaScript you will quickly get familiar with creating variables and functions, assigning values, using and updating them. But, have you ever asked yourself how the JavaScript knows which variable or function is accessible by the given statement, or how are these things managed internally?

This is where _scopes_ and the _scope chain_ come into play. The concept of scopes and scope chain plays a vital role in determining the accessibility of variables and functions. Mastering their intricacies can make the difference between writing clean, maintainable code and struggling with bugs and unexpected behavior.

In this article, we will dive deeper into the inner workings of scopes and explore how they impact your code. That being said, this is not an article for total beginners and basic understanding of JavaScript is required.

---

- [Lexical Scope](#lexical-scope)
 - [Global Scope](#global-scope)
 - [Function Scope](#function-scope)
 - [Block Scope](#block-scope)
- [Nested Scope](#nested-scope)
- [Scope Chain](#scope-chain)
 - [Lookup](#lookup)
 - [Peek under the hood](#peek-under-the-hood)
- [Conclusion](#conclusion)

---

## Lexical Scope

From the perspective of a program, it is very important where you place variable or function declarations, as well as how and when you are using them.

The reason it's important is because of _lexical scope_ and how the program behaves in relation to it. By definition, lexical scope determines the visibility and accessibility of variables and functions based on their position in the source code. It makes code easier to reason about and helps avoid naming conflicts between variables.

In general, JavaScript has different kinds of scopes: **global scope**, **function scope** and, more recently, **block scope**. Some might argue that there is also _module scope_, but we will not discuss it in this article.

You might have also heard about the term **local scope**. It is a more general term that can refer to both function scope and block scope, depending on the context.

### Global Scope

Let's immediately start with an example. Consider the following code.

```javascript
var movies = [
  {
    title: "District 9",
    releaseYear: 2009,
    genre: ["Action", "Sci-Fi", "Thriller"]
  },
  {
    title: "Transformers: Age of Extinction",
    releaseYear: 2014,
    genre: ["Action", "Adventure", "Sci-Fi"]
  }
];
 
console.log(movies);
```

It is easy to spot that the `movies` variable is declared, initialized and printed to console using `console.log`. This is, however, not what I want to point out.

Even now, with a program as simple as this, there is a scope created. This scope is referred to as **global scope**. It exists in **every program** and contains all other scopes that we could possibly define.

Think of it as a container where variables and functions can be declared and other scopes created.


![Global scope container](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3pdlzfespcpgoayipgv.png)

When a variable or function is defined in global scope, it can be accessed from any other scope through the program. There are some exceptions to this, but it should not bother you for now.

There is also one more important aspect of global scope, in JavaScript, called **global object**. Global object **always exists** in the global scope.

![Global object shown inside the global scope](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n496a99iszl2vf4bessq.png)

We all know that JavaScript code can be executed in the browser. However, there are different environments that can run JavaScript code like, for example, Node.js runtime.

Depending on the environment where JavaScript is hosted, a different global object might be present. In browsers, the global object is more commonly known as `Window`, but in Node.js it's referred to as `global`.

Since the global object is part of JavaScript language, in ES2020 specification, it has finally received a unique identifier `globalThis`.

### Function Scope

Whenever you create a function, **function scope** is created along with it. No matter if you use function definition, function expression or even arrow function, function scope is always present.

It exists inside the function body. Every variable, or even other functions, created inside the body will be part of the function scope.

```javascript
var movies = [...]

function printTitle(index) {
  var movie = movies[index]
  var title = movie.title

  console.log(title)
}

printTitle(1)
```

Look at the body of the function `printTitle`, space between `{...}`, it has two variables defined as `movie` and `title`. Since variables are defined inside the function body, they are also part of the function scope.

Also, notice that the `movies` variable is referenced, but it's not defined in the function scope. This variable can still be used by the function, since it's created in the global scope. Remember, every variable created in the global scope is accessible in all other scopes.


![Function scope inside the global scope](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ze6i0055l1k74ooqfd7e.png)

We can see how function scope is created inside the global scope when function is defined. In the image, the global object is removed since we will not talk about it anymore, but keep in mind, it is always there in the global scope.

You might have noticed that the identifier `index` is marked with a blue color. When we defined the function `printTitle`, we did it in the following way `function printTitle(index) {...}`. In other words, it has one parameter called `index` and, by default, every function parameter is added to the function scope.

If you try to access `index`, `movie` or `title` outside function scope, an error will be thrown.

```javascript
var movies = [...]

function printTitle(index) {
  var movie = movies[index]
  var title = movie.title

  // 🖨 Prints to the console
  console.log(title)
}

printTitle(1)

// 🚫 ReferenceError: movie is not defined
console.log(movie)
```

Variables defined in function scope can not be accessed outside the function. When referencing a variable that is not defined in the scope, JavaScript will throw `ReferenceError`.

In the example, we have defined variables using the `var` keyword, but the same behavior would occur for variables defined via `let` or `const` keywords.

### Block Scope

Before ES2015, block scoping did not exist in JavaScript. Reason being that the only way to create variables was using the `var` keyword.

When `let` and `const` keywords are introduced, block scoping has become possible. Having block scoped variables helps a lot to prevent name pollution and accidental overrides of variables that are defined outside the block scope.

> **Not all variables can be block scoped**
Variables defined using `var` keyword can only be function scoped. When used inside a block, such a variable will leak its definition until the first function scope is reached.

Block scope is defined by `{}` and can be put basically in any part of the program. Consider the following code.

```javascript
var movies = [...];

{
  var listOfTitles = movies.map((movie) => movie.title);
  let listOfReleaseYears = movies.map((movie) => movie.releaseYear);

  // 🖨 Prints to the console
  console.log(listOfTitles);

  // 🖨 Prints to the console
  console.log(listOfReleaseYears);
}

// 👇 Leaked definition
// 🖨 Prints to the console
console.log(listOfTitles);


// 🚫 ReferenceError: listOfReleaseYears is not defined
console.log(listOfReleaseYears);
```

Program above is making use of block scope by putting variables inside `{}`. Inside the block scope, variables `listOfTitles` and `listOfReleaseYears` are defined, initialized and printed to console using `console.log`. This works as expected, the content of both variables is printed to the console.

When we try to print them again, outside the block scope, `listOfTitles` will be printed but trying to reference `listOfReleaseYears` will throw a `ReferenceError`. Any idea why is this happening?

If we look back at the code, notice that `listOfReleaseYears` is defined using the `let` keyword. This is the reason why the variable `listOfReleaseYears` can not be used outside the block scope where it was initially created. On the other hand, `listOfTitles` is defined using the `var` keyword and it does not _see_ block scope.

Using `{}` to create block scope might not be the most common way to do it. More often than not, block scopes will be created using statements like `if...else` or `switch`. In fact, `for` and `while` loops are also block scoped. It often can catch developers off guard since they might think that the scope inside the loops behaves the same as the function scope, which is not the case.

```javascript
var movies = [...];

function printTitle(index) {
  var movie = movies[index];
  var title = movie.title;

  if (title.length > 12) {
    const truncatedTitle = `${title.substring(0, 12).trim()}...`;

    // 🖨 Prints to the console
    console.log(truncatedTitle);
  }

  // 🚫 ReferenceError: truncatedTitle is not defined
  console.log(truncatedTitle);
}

printTitle(1);

```

The variable `truncatedTitle` is defined inside the block scope created by the `if` statement, and it will be accessible as long as we reference it from the block scope. However, if we try to reference `truncatedTitle` outside the block scope, in function scope, `ReferenceError` will be thrown.

To get a better picture, how block scope fits into our program, let's visualize it to show scope boundaries.


![Block scope defined inside function scope](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5py87ox3b10yeo46tod.png)

If you look at the image and the source code, can you spot similarities? It should be obvious that scopes are defined by the source code. When you write your program, think about scopes as well. They will always be involved.

## Nested Scope

In most non-trivial programs, chances are, many of the so-called _nested scopes_ will probably be created. Nested scope does not represent a new kind of scope, remember, there are only global, function and block scopes in JavaScript.

The term **nested scope** is used to describe a situation in which one scope is defined inside another scope. With that being said, we have already encountered nested scope in our previous examples.

```javascript
var movies = [...];

function printTitle(index) {
  var movie = movies[index];
  var title = movie.title;

  if (title.length > 12) {
    const truncatedTitle = `${title.substring(0, 12).trim()}...`;
    console.log(truncatedTitle);
  }

  console.log(title);
}

printTitle(1);
```

Can you spot which scope would be considered as nested? It is a block scope created by the `if` statement.

As we already know, the function `printTitle` will create a function scope. However, it only has global scope as its outer scope. When scope is defined directly inside the global scope, it is usually not considered to be nested.

The global scope is the outermost scope and any other scopes will, by default, be created inside it. For this reason, nesting usually implies a scope to be defined inside another scope that is not the global scope.


![Visualized nested scope](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yt63jedhg6l9rew1ihuy.png)

In our example, the block scope ticks all the boxes. It is nested inside another scope, created by the `printTitle` function, and is not directly in the global scope.

Many nested scopes can be defined through the program. This creates a hierarchical relationship between the scopes, where the inner scope has access to its own variables as well as the variables of outer scopes. This nesting of scopes allows for more complex programs to be built, as it guarantees separation of concerns and the creation of reusable code.

Let's get back to our program and shed some light on scope related terminology. If we look from the perspective of the `printTitle` function, we can say that the block scope, created by the `if` statement, is a _child_ or _inner_ or _nested_ scope. In the same note, the function itself has the global scope as its _parent_ scope.

Continuing, if we look from the perspective of the block scope, the function scope will be its _parent_ scope or the _first outer_ scope while the global scope will be it's _second outer_ scope.

Another important term is _current scope_. It refers to the scope where the JavaScript engine is currently executing code. So, hypothetically, if the engine is executing code in the block scope, we can refer to it as the current scope.

If you find it a bit hard to understand nested scope and related terminology, try to read it again at a slower pace and don't give up so easily.

## Scope Chain

So far, we have talked a lot about lexical scope and how it works, but there is one more important topic, closely related to lexical scope, that we need to cover.

We have said that scope determines visibility and accessibility of variable and function declarations. You might be thinking, but how does it work? It has something to do with the so-called **scope chain**.

Scope chain is a mechanism used to determine the accessibility of variables and functions through the program. It defines the order of scope resolution during the runtime/execution phase. To better understand this, let's talk about scope chain _lookup_ first.

### Lookup

When a referenced variable or function can not be found in the current scope, the engine has to perform a search in the scope chain. This search is usually called scope chain **lookup**.

```javascript
var movies = [...];

function printTitle(index) {
  var movie = movies[index];
  var title = movie.title;

  if (title.length > 12) {
    const truncatedTitle = `${title.substring(0, 12).trim()}...`;
    console.log(truncatedTitle);
  }

  console.log(title);
}

printTitle(1);
```

Take a look at the function scope, the variable `movies` is referenced, but it is not defined inside the function scope. The same is for the block scope, the variable `title` is referenced, but it is not defined in the block scope.

So how does the engine know what is the value of each of those variables? This is where scope chain lookup comes into play.

When a variable or function is referenced, the engine starts at the current scope, looking for definition. Remember, current scope is the scope where the engine is currently executing the code. If the reference is not resolved in the current scope, the engine moves up in the scope chain searching for the reference. This process is repeated until the reference is resolved or the end of the scope chain is reached. In case that the reference could not be resolved, the engine will throw `ReferenceError`.


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

The variable `movies` is referenced in the function scope. Since it is not defined in the same scope, it could not be found. The engine moves upwards in the scope chain and tries to resolve the reference. The next scope in the chain is the global scope. Since the variable `movies` is defined in the global scope, the engine can resolve the reference and get the value.

Similarly, when the engine tries to access `title` from block scope, it has to do a lookup, go upwards in the scope chain trying to resolve the reference. In this case, the reference will be resolved in the function scope since the variable `title` is defined in there.

To recap, scope chain lookup refers to the process of searching through a list of scopes to find a referenced variable or function. The engine will start at the current scope and move upwards the scope chain until the reference is resolved or `ReferenceError` is thrown.

### Peek under the hood

At this point, you should have a better understanding of lexical scope and scope chain. It's time to go one step deeper and look under the hood to see how the engine creates and maintains a scope chain.

Scope chain is constructed and maintained during the execution phase. Just before the engine starts executing a piece of code, it needs to create a structure that holds variables and functions declarations. This structure is known as the lexical environment.

![Visualization of lexical environment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t4n69b05gucdyemrup6s.png)

Before you ask, yes, lexical environment is related to lexical scope. It holds information about identifiers and their values that are defined in lexical scope.

Each lexical environment has a reference to its parent environment, creating the scope chain. Do not be concerned about how the engine implements scope chain structure under the hood, after all, it's a job of the engine.

![Scope chain constructed using references between lexical environments](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8psc8h2030z67bpmplas.png)

Now, when we have a good picture of how it works, let's use the same code from previous examples and walk through steps that the engine might take to create the scope chain.

This will not be a detailed description on how the engine executes the code. Our goal here is to point out parts that are important for the scope chain creation.

```javascript
var movies = [
  {
    title: "District 9",
    releaseYear: 2009,
    genre: ["Action", "Sci-Fi", "Thriller"]
  },
  {
    title: "Transformers: Age of Extinction",
    releaseYear: 2014,
    genre: ["Action", "Adventure", "Sci-Fi"]
  }
];

function printTitle(index) {
  var movie = movies[index];
  var title = movie.title;

  if (title.length > 12) {
    const truncatedTitle = `${title.substring(0, 12).trim()}...`;
    console.log(truncatedTitle);
  }

  console.log(title);
}

printTitle(1);
```

Obviously, code first needs to be parsed and compiled. At this point, the engine already knows about lexical scopes. Just before execution is started, the engine will create the global lexical environment.

One important step, more commonly known as **hoisting**, will occur during the creation of every lexical environment. The engine will hoist variables and functions to the top of their respective scopes and assign them their default values.

> Hoisting behaves differently for variables defined using `let` and `const` keywords.

Taking the hosting into account, a freshly created global lexical environment, for our program, might look like this.

```text
{
  movies: undefined,
  printTitle: <ref. to function printTitle>,
  outer: null
}
```

Finally, the engine starts executing the code, line by line. It will start with `var movies = [...]`. Since the `movies` variable is already created, courtesy of hoisting, the engine will create an array of objects in memory and save the reference to that array into the `movies` variable.

Next, the engine will see the `function printTitle` part of the code. However, there is nothing that needs to be done here. During hoisting, the engine has already created the `printTitle` identifier and assigned, to it, a reference to the `printTitle` function.

Good, the engine has made some progress and the global lexical environment looks like this now.

```text
{
  movies: <ref. to array>,
  printTitle: <ref. to function printTitle>,
  outer: null,
}
```

The variable `movies` has been updated, and it now holds the reference to the array of movies saved in the memory.

It is important to point out that the global lexical environment contains `movies` and `printTitle` identifiers only because we have put them into the global scope.

![Global lexical environment populated with identifiers from the global scope](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/de1dvrdxwrbbdk5rh7v9.png)

Did you notice the `outer` identifier in the global lexical environment? This identifier is injected by the engine whenever a new lexical environment is created. It has a value of `null` since the engine is executing code in the global scope.

Continuing, the engine will see that we have called the `printTitle` function passing number `1` as an argument. Calling a function will trigger function execution. However, before the code inside the function gets executed, the lexical environment for the function itself will be created and hosting, for the function scope, will occur.

```text
{
  index,
  movie: undefined,
  title: undefined,
  outer: <globalLexicalEnvironment>
}
```

Since the function has received `1` as the first argument, the engine will immediately initialize the parameter `index` and set its value to `1`.

Notice that `outer` identifier has reference to the global lexical environment. This reference allows the engine to access the global lexical environment. You know what that means? That's right, the engine has just created the scope chain.

![Scope chain created by linking two lexical environment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/enyv1rvek7h8vatknzs3.png)

When executing `var movie = movies[index]` the engine will see that the variable `movies` does not exist in the current scope, and it will use the reference, saved in the `outer` identifier, to access the global lexical environment. Now, it will start looking for the `movies` identifier and resolve its value. Does this sound familiar? The engine has just performed a scope chain lookup.

The variable `movies` holds the reference that points to the movies array saved in memory. According to our code, `movies[index]`, the engine will take the object on the index `1` and save its reference into the `movie` variable. At the end, reference to the second object in the `movies` array is saved into the `movie` variable.

Continuing, statement `var title = movie.title` is next to execute. To initialize the `title` variable, the engine will need to resolve the `movie` identifier. Since it's declared in the local scope, scope chain lookup will not be required. As we already know, the variable `movie` holds reference to the `movies[1]` object. The engine will take the value from the property `title` which is string `"Transformers: Age of Extinction"` and save it into the `title` variable.

> **Reference or value?**
It is very important to know when the engine is passing/returning reference and when the actual value. In general, only primitive data types are passed by value, and non-primitive ones are passed as a reference.

The engine has just executed the first two lines inside the function. Here is how the local (function) lexical environment looks like now.

```text
{
  index: 1,
  movie: <ref to movies[1]>,
  title: “Transformers: Age of Extinction”,
  outer: <globalLexicalEnvironment>
}
```

Both `movie` and `title` are updated. Notice that the `movie` variable now holds reference to the `movies[1]` object, while `title` holds the string value `"Transformers: Age of Extinction"`.

Woah, the engine is flying through our program and we are already in the `if (title.length > 12)` part of the code. When the engine checks `title.length > 12`, it will get `true` as a result. Remember, `title` is still part of local (function) scope, so no scope chain lookup is required.

Because of the truthful condition, the engine will create a new lexical environment for the `if` statement. Remember from before, this lexical environment is created only because we used `const` inside the `if` statement.

After creating a new lexical environment for the `if` block, taking hoisting into consideration, the engine will give us something like this.

```text
{
  truncatedTitle,
  outer: <functionLexicalEnvironment>,
}
```
The engine has created yet another lexical environment, and look where does the `outer` identifier points to? That's right, to the function lexical environment, and now we have scope chain that consists of 3 lexical environments.

![Scope chain with three lexical environments](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r20nkw2qc9sgw4h6b798.png)

Notice that `truncatedTitle` is not initialized with any value. At the moment, `truncatedTitle` is in the so-called **TDZ** (Temporal Dead Zone). The engine knows about the `truncatedTitle` variable, but it can not be used before it's initialized or `ReferenceError` error will be thrown. This only happens when using variables declared with `let` and `const`.

The engine starts executing the first line of code inside the block ``const truncatedTitle = `${title.substring(0, 12).trim()}...` ``. Here is where the engine will initialize the `truncatedTitle` variable and, after this point in time, TDZ for `truncatedTitle` ends.

The engine will search the scope chain to resolve the `title` identifier and get the value. This identifier will be resolved in the function scope. Because of `substring` and `trim` string methods, the engine will return the first 12 characters, removing potential empty space along the way. At the end, the value saved into the `truncatedTitle` will be string `"Transformers..."`.

```text
{
  truncatedTitle: “Transformers...”,
  outer: <functionLexicalEnvironment>,
}
```

Just before the end of the block scope, `console.log(truncatedTitle)` will be executed and the value of the `truncatedTitle` variable will be printed to the console.

Continuing, the engine has to exit the block scope. Usually, when the engine exits a lexical scope, the lexical environment for that scope is typically destroyed and garbage-collected by the engine's memory management system. This means that any variables and functions declared in that scope are no longer accessible and do not occupy memory. This is an automatic process that happens in the background and usually the developer does not have to worry about it.

Taking that into account, when the engine exits the block scope, the block lexical environment will be destroyed, which means that variable `truncatedTitle` no longer exists and its value `"Transformers..."` is removed from the memory.

If we take a look at the scope chain, the lexical environment created for the block scope is removed from the scope chain. The engine will manage this automatically when the lexical environment is destroyed.

![Scope chain after removing lexical environment created for the block scope](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0l68bvcy8xd5x65g1onq.png)

Next in order of execution is `console.log(title)`. The engine is executing in the function scope again. This means that the variable `title` is accessible from the local lexical environment and scope chain lookup is not required. When the engine is done executing this line, the value of the variable `title` will be printed to the console.

Continuing, the engine has to exit the function and garbage-collection will kick in by destroying the lexical environment and releasing used memory. Now, if we look at the scope chain, it has only one lexical environment left.

![Scope chain after removing lexical environment created for the function scope](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ratnd49uh1v91nlal8b.png)

Finally, there is no code left to be executed, and the engine has reached the end of our program. It terminates the process and releases all the resources that were used during execution time, including memory and CPU time.

## Conclusion

Lexical scope and scope chain are fundamental concepts in JavaScript. Lexical scope determines which variables and functions are accessible within a certain code block.

The scope chain, on the other hand, is an internal structure managed and used by the JavaScript engine to resolve variables and functions during runtime, following the hierarchy of lexical scopes. Understanding these concepts is important for writing efficient and effective JavaScript code.

I tried to give more details about lexical scope and related terminology, but also to dive deeper into scope chain management and explain how it works under the hood. I really hope that this was helpful and you have more knowledge about this topic than before reading this article.

---

Check out other articles on [my blog](https://ramomujagic.com/blog/).