The Ultimate Guide to Execution Contexts, collieries, Scopes, and Closures in JavaScript

The Ultimate Guide to Execution Contexts, collieries, Scopes, and Closures in JavaScript

Perhaps surprisingly, I think the most important basic concept for understanding the JavaScript language is understanding the Execution Context. Learning execution context properly makes it easier to learn more advanced things like variable promotion, Scope chains, and closures. So what exactly is an “execution context”? To understand it better, let’s first look at how we write software.

One strategy for writing software is to break up the code into separate pieces. Although these “chunks” have different names (functions, modules, packages, and so on), they serve the same purpose — to break down and deal with the complexity of the application. Now let’s not think in terms of writing code, but in terms of JavaScript engines, which interpret code. So can we handle the complexity of interpreting code by using the same strategy we used when we wrote code, by breaking it up into chunks? The answer is yes, and these “blocks” are called execution contexts. As you can use the function, module, package (functions provides/modules/packages) to deal with the complexity of the code, JavaScript engine can be handled by the execution context explain the complexity and running the code. Now that we know what execution contexts are for, the next question to answer is: how are they created and what are they made of?

The first Execution Context created when the JavaScript engine executes code is called the Global Execution Context. Initially the execution context contains two things — a global object and the this variable. This refers to the global object, which is window when JavaScript is executed in the browser and global when executed in the Node environment.

As you can see in the figure above, even without any code, the global execution context still contains two things — window and this. This is the most basic form of a global execution context.

Let’s take a step by step look at what happens when you add code to your program. Let’s add some variables first.

Can you see the difference between the two pictures above? The key is that each Execution context has two separate phases — Creation and Execution — with each phase having its own unique responsibilities.

During the global creation phase, the JavaScript engine will:

  1. Creating a global object
  2. createthisobject
  3. Set up memory space for variables and functions
  4. The variable is declared and assigned by default toundefined, while placing all function declarations in memory.

It is not until the execution phase that the JavaScript engine starts executing the code line by line.

We can see the flow from the creation phase to the execution phase in the GIF below.

During the creation phase, window and this are created, variable declarations (name and handle) are assigned to undefined by default, and all function declarations (getUser) are put into memory. Then, once in the execution phase, the JavaScript engine starts executing the code line by line and assigns real values to variables that already exist in memory.

Gifs are cool, but executing code step by step and seeing the execution for yourself is even cooler. I created JavaScript Visualizer for you, you deserve it. If you want to see the exact code above, open this link.

To really consolidate the knowledge of the creation and execution phases, let’s print out some post-creation and pre-execution values.

console.log('name: ', name)
console.log('handle: ', handle)
console.log('getUser :', getUser)

var name = 'Tyler'
var handle = '@tylermcginnis'

function getUser () {
  return {
    name: name,
    handle: handle
  }
}
Copy the code

In the above code, what do you want to print in the console? When JavaScript starts executing the code line by line and calling console.log, the creation phase is complete. This means, as you saw earlier, that the variable declaration is already assigned to undefined, while the function declaration is completely in memory. So, as expected, name and handle are undefined, and getUser refers to an in-memory function.

console.log('name: ', name) // name: undefined console.log('handle: ', handle) // handle: undefined console.log('getUser :', getUser) // getUser: ƒ getUser () {} var name = '@tylermcginnis' var handle = '@tylermcginnis' function getUser () {return {name: name, handle: handle } }Copy the code

The process of assigning the default value of undefined to variable declarations during creation is called variable promotion (as of 1997)

You may have tried to explain “variable promotion” to yourself before, but it didn’t work. The confusing thing about “variable lifting” is that nothing really “lifts” or moves. Now that you understand that execution context and variable declarations are assigned to undefined by default during the creation phase, you also understand “variable promotion” because it is “variable promotion”.


By now, you should be fairly familiar with the global execution context and its two phases, creation and execution. The good news is that there is only one other execution context you need to learn, and it is almost identical to the global execution context. This is the function execution context, which is created when the function is called.

Crucially, the execution context is only created when the JavaScript engine first starts interpreting the code (the global execution context) or when a function call is made.

Now the main question is, what is the difference between a global execution context and a function execution context? If you remember, it was mentioned earlier that during the global creation phase, the JavaScript engine will:

  1. Creating a global object
  2. createthisobject
  3. Set up memory space for variables and functions
  4. Variable declaration defaults toundefined, while the function declaration is stored in memory

Which of these steps are incorrect for the function execution context? Step 1. We have one and only global object, which is created during the creation phase of the global execution context, not when the function call and JavaScript engine create the function execution context. A function execution context does not need to create global variables and instead needs to consider arguments, whereas a global execution context does not. With this in mind, we can adjust the previous list. When the function performs context creation, the JavaScript engine will:

  1. Creating a global object
  2. Create a parameter object
  3. createthisobject
  4. Set up memory space for variables and functions
  5. Variable declaration defaults toundefined, while the function declaration is stored in memory

Let’s go back to the code mentioned earlier and look at the process, but this time in addition to defining getUser, let’s look at what happens when you call it.

View visual code

As we said, a new execution context is created when getUser is called. During the creation phase of the getUser execution context, the JavaScript engine creates this object and arguments object. Because getUser doesn’t have any variables, JavaScript doesn’t need to set the memory space and “boost” the variables.

You may also have noticed that when the getUser function completes, it is removed from the visualization. In effect, the JavaScript engine creates an “execution stack” (also known as a “call stack”). When a function is called, a new execution context is created and added to the execution stack. When the function completes its execution and completes its creation and execution phases, it is popped from the execution stack. Because JavaScript is single-threaded (meaning that only one task can be executed at a time), this process is easy to visualize. Using JavaScript Visualizer, execution stacks are displayed in nested forms, with each nested item corresponding to a new execution context within the execution stack.

View visual code


Now we know how function calls create their own execution context and add it to the execution stack. What we don’t know yet is what happens when you have local variables. We modify the code so that the function has local variables.

View visual code

There are some important details to note here. The first is that any arguments you pass in are added as local variables to the execution context of the function. In the example, handle appears as a variable in the global execution context (where the variable is defined) and also in the getURL execution context because it is passed in as a parameter. Next, variables declared inside functions exist in the context of function execution. So when we create the twitterURL, it exists in the getURL execution context — where it is defined, not in the global execution context. This may seem obvious, but it’s the rationale for our next topic, scopes.


In the past, you’ve probably heard the definition of “scope” as “the place where a variable is accessible.” Now whether this definition is correct or not, using your new knowledge of execution context and JavaScript Visualizer tools, the concept of scope should become clearer than ever. In effect, MDN defines “scope” as “the context of the current execution.” Sound familiar? We can think of “scope” and “places accessible to variables” in terms of execution context.

Here’s a test. What does the bar print in the following code?

function foo () {
  var bar = 'Declared in foo'
}

foo()

console.log(bar)
Copy the code

We used JavaScript Visualizer to verify.

View visual code

When foo is called, we create a new execution context in the execution stack. Create this, arguments, and assign bar to undefined during the creation phase. It then starts the execution phase by assigning the string Declared in foo to bar. At the end of the execution phase, the Foo execution context pops out of the stack. When foo is removed from the execution stack, we try to print the bar on the console. In this case, the JavaScript Visualizer shows that bar never appears, so we get undefined. This tells us that variables defined inside a function are locally scoped. This means (for most, we’ll see exceptions later) that once the function execution context is popped off the stack, the variable is not accessible.

Here’s another test. What will the console print after the following code is executed?

function first () {
  var name = 'Jordyn'

  console.log(name)
}

function second () {
  var name = 'Jake'

  console.log(name)
}

console.log(name)
var name = 'Tyler'
first()
second()
console.log(name)
Copy the code

Let’s take a look at JavaScript Visualizer.

View visual code

We get: undefined, Jordyn, Jake, and Tyler. This tells us that we can think of each new execution context as having its own unique variable environment. Even if there are other execution contexts that contain the variable name, the JavaScript engine looks for the variable from the current execution context first.

This raises the question, what if there are no variables in the current execution context? Does the JavaScript engine stop looking for that variable? Let’s look at an example that will give us the answer. What will the following code print?

var name = 'Tyler'

function logName () {
  console.log(name)
}

logName()
Copy the code

View visual code

You might be intuitively told to print undefined because there is no name variable in the scope of the logName execution context. This is normal, but wrong. What happens if the JavaScript engine can’t find a variable in the context of function execution? It looks for the variable in the context of the most recent parent execution. The search chain continues until the engine finds the global execution context. In this case, if the global execution context also does not have the variable, a Reference Error will be thrown.

If the variable does not exist in the local execution context, the JavaScript engine checks the respective parent execution context one by one, a process called scoped chain. Each new execution context is indented and given a special background color, as shown in JavaScript Visualizer. Through visualization, you can see that each child execution context can refer to any variable in its parent execution context, but not vice versa.


As we learned earlier, variables defined inside a function are locally scoped and cannot be accessed (for the most part) when the function execution context is ejected from the execution stack. One case where this statement is wrong is when a function is embedded in another function. In this case, the child function can retain access to the outer function scope even if the parent function’s execution context has been removed from the execution stack. Well, that’s complicated. Again, use JavaScript Visualizer, which can help us.

View visual code

The JavaScript Visualizer creates the Closure Scope after the makeAdder execution context pops up on the execution stack. Have the same variable environment in the Closure Scope as in the makeAdder execution context. The reason for this is that we’re embedding a function into another function. In our case, the function inner is embedded in the function makeAdder, so inner creates a closure that contains the makeAdder variable environment. Because the Closure Scope is created, inner can access the variable X (through the Scope chain) even after the makeAdder execution environment has been ejected from the execution stack.

As you might expect, a child function “contains” the variable environment of its parent function, a concept called a “closure.”