Hoisting de-mystified

Elizabeth Woodhouse
4 min readJul 8, 2021

As I was studying up for interviews I came across an interview question asking me to define hoisting. Many sites defined hoisting as “JavaScript’s default behavior of moving declarations to the top.” I could just quote this answer back for interviews, but then I started to ask myself: What does “moving declarations to the top” actually mean? What about JavaScript causes hoisting to occur and when does this actually happen? How would learning this concept help me become a better engineer?

To better understand hoisting, I knew I had to better understand what happens under-the-hood in JavaScript. When the computer executes JavaScript code, the JavaScript engine creates a global execution context. Each execution context has two phases: a compile phase and an execution phase. The compile phase is when source code is converted to executable code. The execution phase or run phase is when that executable code starts to run.

During the compile phase, JavaScript creates a memory heap for all the variable and function declarations that is in it’s execution context. JavaScript does not actually store the value or type into it’s memory. The space is just allocated for the declaration. This is what hoisting is, when variables and function declarations are “hoisted” or “moved to the top” of it’s execution context. So essentially, it’s when space is created in the current execution context for the variable and function declarations. (Keep in mind for let variables, the JavaScript engine does assign storage space, but does not initialize variables to undefined. For var variables, JavaScript initializes variables to undefined.)

Once the compile phase finishes the engine moves on to the execution phase- the phase where executable code starts to run. During the execution phase the JavaScript engine goes line by line and runs the code and assigns the values and types of declarations to the variables. When the engine comes across a function call, it will create a new local execution context nested within the functions parent execution context and run the same compile and run phases. By understanding what happens during the compile and execution phase, you can better understand hoisting.

Consider how JavaScript engine would run this code:

var one = 1;function addTwo(num) {
return num + 2;
}
var sum = addTwo(one);
console.log(sum);

The JavaScript engine would run the compile phase and create a global execution context. It would then set-up a memory heap for all the variables and functions. It would hoist the declarations to the top of the execution context and the memory heap would look like this:

one: undefined
addTwo: function(){}...
sum: undefined

Then the JavaScript engine would run the execution phase and run the code line by line. Eventually invoking the addTwo function, creating a functional execution context and running its own compile and execution phase until addTwo(one) becomes the value of the invocation expression, which would be 3.

Let’s consider another example:

function addTwo() {
num = 4;
return num + 2;
var num;
}
var sum = addTwo();
console.log(sum);

At first glance you may assume that this would console.log an error. But because of hoisting, it comes back as 6. The declaration of num is hoisted to the top of the addTwo execution context during the compile phase and initialized as undefined. And during the execution phase num is initialized to 4.

Let’s re-consider this example but declare with let instead:

function addTwo() {
num = 4;
return num + 2;
let num;
}
let sum = addTwo();
console.log(sum);

While hoisting does occur for let variables during the compile phase, where the JavaScript engine assigns memory storage for these variables. The JavaScript engine does not initialize these variables to undefined. These variables will be in the temporal dead zone until they are defined. So for the example above you will receive a reference error that you cannot access 'num' before initialization instead of getting back 6.

By having a better understanding of what’s happening under the hood with JavaScript you’ll be able to better de-bug and understand how your code is being run. You’ll also have a better understanding between the differences between compile time versus run-time(execution) errors. My explanation was definitely a simplified version of what goes on with JavaScript. But as I continue to grow a developer, I will keep digging in to try to demystify topics. If you have any suggestions on edits for my post, please reach out as I’m always trying to grow and learn!

Reference material:

Articles:

Videos:

--

--