← Back

The ultimate guide to understand hoisting in Javascript

August 9, 2024

If you are coming from other languages such as Dart, C++, C#, Python, or Java, you might have encountered an error if you try to access the variable or call a function before it is initialized. This is not the case in JavaScript.

Let's take the following example:

var a = 10;
function print() {
  console.log("Hello world");
}

In the above code, we declare a global variable a and assigned a value to it. We also created a function print that simply prints the text to the console. If we try to print the value a to the console, we would simply write console.log at the end of the code. Similarly, if we want to call the function, we simply need to call it.

var a = 10;
function print() {
  console.log("Hello world");
}
console.log(a)
print()

What would be the output of the above? Yes, you guessed it right.

It prints the value of a and calls the print method. But what if we try to place the last two lines of code at the top of the code? What would be printed on the console?

console.log(a);
print();
var a = 10;
function print() {
  console.log("Hello world");
}

Any idea why are we getting undefined instead of the value of a? Well, let me explain it. This is due to a phenomenon known as Hoisting.

Hoisting is the process by which the interpreter moves all the variable and function declarations to the top of the script. This allows us to use variables and functions before they are declared. To better understand hoisting in JavaScript, we need to know how code is executed under the hood in JavaScript.

How JavaScript code is executed


Whenever we run a piece of JavaScript code, a global execution context is created. An important point to remember is that everything inside JavaScript happens inside execution content. You can think of execution context as a container that encapsulates the JavaScript environment. Back to the global execution context, a global execution context comprises two main phases i.e., memory allocation phase and code execution phase.

Let’s run our code keeping in mind the global execution context. In our code, we are declaring a variable named a, and a function named print . When we run the code, a global execution context is created. What the JavaScript engine does is it identifies the variables and functions and then allocates them someplace in the memory. These variables and functions are stored as key-value pairs in the memory.

Note that when a is allocated space in memory, it is assigned a value of undefined. undefined is a special placeholder that is assigned by the JavaScript engine when variables are allocated memory during the memory allocation phase. In the case of functions, it is not assigned undefined, rather the whole function code is placed inside the memory of the function.

Once the variables and functions are assigned memory, it’s time for the code execution phase. In the code execution phase, the code is executed one line at a time.

1  var a = 10;
2  function print() {
3   console.log("Hello world");
4  }
5  print()

Let’s execute the above line by line in the code execution phase. When the JavaScript engine executes line 1, it changes the value of a from undefined to 10.

When this is done, it moves to line 2, where it finds the function declaration. There is nothing to execute in the function as it is simply a function declaration and skips the code from lines 2 to 4. It then moves to line 5 where the function is being invoked and a new execution context is created for the function.

In the new function execution context, we again have two phases, the memory allocation phase, and the code execution phase. As there are no variable declarations inside the function, the JavaScript engine allocates no memory. It then passes onto the next phase, which executes the function line by line. Inside the function, we are only printing the statement onto the console. When the console statement is run, the function execution context is destroyed and the control is passed back to the global execution context.

Wow, that’s a lot to digest. Hope you might have got some idea of how JavaScript works under the hood.

Now let’s get back to hoisting.

console.log(a);
print();
var a = 10;
function print() {
  console.log("Hello world");
}

When we move the console statement and function invocation to the top, we get undefined for the value a. As we saw in the global execution context, when code is run, the variables are allocated memory and assigned with the value of undefined. Let’s observe it in the browser dev tools.

When we put a debugger at line 1, we observe that inside the Global scope, a is undefined as we have discussed previously. The console statement prints undefined to the console.

We move the debugger to the next line, which will point to the function invocation.

The function invocation would print Hello world to the console.

a is still undefined, as it is never assigned a value. We move the debugger to the next line where a is assigned the value of 10.

We still get undefined for a. This is because we haven’t executed line 4 yet. When we execute line 4, we can see that the value of a is changed from undefined to 10.

Conclusion

We have seen what is hoisting in JavaScript and understand how JavaScript works behind the scenes. If you have any queries, let me know in the comment section.

Happy coding!!!

Salman Inayat Email 𝕏 GitHub