loading...
Cover image for Understanding Hoisting

Understanding Hoisting

skaytech profile image skaytech Updated on ・4 min read

Introduction

Before we talk about hoisting, I think it is important to understand how the JavaScript engine looks at the code, interprets, and runs it. Once we understand this, hoisting becomes straight forward to explain.

Execution Context

JavaScript engine breaks up the code into smaller segments to manage the complexity of interpreting and running it. If I have to explain this easier, think about a scenario where you attempt to write a web application. Your application typically would comprise of modules, functions, variable declarations, etc. Essentially, you have broken down the application into logical pieces in order to understand, maintain, and debug it.

Just like how modules, functions, etc. allow you to manage program complexity, Execution Context is the JavaScript engine's way to manage the complexity of interpreting the code. Hope makes things a little clearer.

Global Execution Context

The first execution context that gets created when the JavaScript engine runs your code is called the “Global Execution Context”. Initially, this Execution Context will consist of two things - a global object and a variable called 'this'.

Execution Context

The above image represents the global execution in the most basic form. The 'this' keyword references the global object which is the 'window' object.

Creation & Execution Phases

Now that we understand the global execution context, let us understand the two phases that exist while running any JavaScript program.

Let us consider the following code example:

var fruit = apple;

function getFruit() {
    return fruit;
}

Creation Phase

The below diagram depicts how the Global Execution Context looks during the creation phase.

Creation Phase

In the Global Creation phase, the JavaScript engine will:

  1. Create a global object.
  2. Create an object called “this”.
  3. Set up memory space for variables and functions.
  4. Assign variable declarations a default value of “undefined” while placing any function declarations in memory.

Execution Phase

The below diagram depicts how the Global Execution Context looks during the execution phase.

Execution Phase

In the Global Execution Phase, the JavaScript engine will:

  1. Starts running the code line by line.
  2. Assigns the 'real' values to the variables already present in the memory.

Now that we've understood the creation & execution phases, let us take another example and look at the output on the console.

console.log(`The fruit is ${fruit}`);
console.log(`The color is ${color}`);

var fruit = 'apple';
var color = 'red';

function getFruit() {
    return fruit;
}

function getColor() {
    return color;
}

//Output
//The fruit is undefined
//The color is undefined

Things to note:

  • During the creation phase, the variables 'fruit' & 'color' are initialized with the values 'undefined'.
  • Hence when the console.log statement is encountered, the value 'undefined' is printed on the console.

Hoisting

The process of assigning variable declarations a default value of 'undefined' during the creation phase is called Hoisting.

The thing that’s confusing about “hoisting” is that nothing is actually “hoisted” or moved around. A lot of other explanations out there talk about how the code variables and functions are moved up the stack before execution without clearly talking about the creation and execution phases in the execution context.

A quick example of the below code will make sense after understanding hoisting.

//The Variable x is initialized
x = 5;

//Output the value of x multiplied by 2 on the console
console.log(x * 2);

//The variable x is declared over here
var x;

//Output -> 10

In the above code example, you would notice that the variable 'x' is initialized in the first statement and then in the last statement there's a declaration of x. But, when JavaScript engine is in the creation phase, it moves up the declaration statement to the top of the stack and hence the above program would look like below when the JavaScript engine runs it.

//The variable x is declared over here
var x;

//The Variable x is initialized
x = 5;

//Output the value of x multiplied by 2 on the console
console.log(x * 2);

//Output -> 10

Ideally, I would have expected the JavaScript engine to throw an error for using a variable before it has been declared, but thanks to ES6, this problem has been addressed with let & const. You can learn more about let & const over here.

What is not Hoisted?

We say that the variable declarations using let & const are not hoisted. In addition, the following are also not hoisted by the JavaScript engine:

  • Functions defined with an expression.
  • Arrow Functions.

Conclusion

Hoisting is nothing but the process of assigning variable declarations a default value of 'undefined' during the creation phase.

I would suggest you that now that you immediately jump to my article on Closures, since it's a continuation of the concepts described in this article. You can read it over here.

I hope you enjoyed this article. Do let me know your feedback and do not share it with your friends.

You may also be interested in:

Posted on by:

skaytech profile

skaytech

@skaytech

Engineering Manager/Product Manager & an amateur Musical Keyboardist...

Discussion

markdown guide
 

This is indeed a very clear description - I would say though that definitions of a let or const are hoisted, but are not allowed to be accessed for read or write before first definition. This is a subtle difference ofc.

function someFunction() {
     function innerFunctionThatMakesAClosure() {
         return value;
     }
    let value = 1000;
    return innerFunctionThatMakesAClosure;
}
console.log(someFunction()()); // -> 1000

The definition was hoisted to create the closure, as the inner function appears lexically before the declaration of the variable.

 

Very good point Mike, but I think some terminology needs to be clarified. Variable declarations (variable name bindings to memory) with let and const are hoisted, not their definitions (i.e. function definitions). But they cannot be accessed for read/write before their first assignment (initialization).

The provided example is pretty neat, here's a more basic example without closures:

console.log(randomVar); // console will return Uncaught ReferenceError: randomVar is not defined
console.log(hoistedVar); // console will return Uncaught ReferenceError: Cannot access 'hoistedVar' before initialization
function log() {
  console.log(hoistedVar); // console will not return any error as this line isn't actually executed yet, so hoistedVar is not being accessed
}
log(); // console will return Uncaught ReferenceError: Cannot access 'hoistedVar' before initialization
let hoistedVar = "Hi"; // hoistedVar is initialized here
log(); // console will log "Hi"

Note that the ReferenceError from trying to access the hoisted "let" variable declaration is different than the ReferenceError from trying to access a variable that has not been declared at all.

So to reiterate the original point made, all variable declarations are hoisted, but let and const variable declarations will give a ReferenceError if the variable is accessed before being initialized.

 

Yes. Agreed. This was also mentioned previously by Mike in the comment below. I'll update the article with this comment.

 

Thanks for the clarification Mike!! Glad you liked the explanation.

 

It's a really nice article :)

 

Very well explained context ;), and the examples and diagrams were clear and concise.

A lot of other explanations out there talk about how the code variables and functions are moved up the stack before execution without clearly talking about the creation and execution phases in the execution context.

That was literally what I had in mind when I wanted to write an article about hoisting, but I'm so glad I don't have to now because you've already done so better than I ever could have.

 

Oh my god! Thank you means a lot :-) Do checkout my other post regarding 'Understanding Closures'. I've just taken the same explanation forward and added functional execution context and explained further. I'm hoping you'll ike that one too.

 

Wow, execution context, lexical scope and closures were the biggest topics I wanted to write about. It's like you've made my dreams into a reality. Thank you lol! I'll be sure to check it out later and save these two articles for whenever I need a resource to refer somebody else to. I believe that many new Javascript developers don't have a solid grasp on these fundamentals because they learn them without really understanding the "context" of what's going on under the hood (of the Javascript engine) ;). So your articles are gold!

Thank you so much! Much appreciated 👍 I'm writing one more next week on 'this', 'bind', 'call'. I'm just ensuring I'm reading up first and understand them before writing them up. So, stay tuned!!

Ah yes, 'this', how could I have forgotten about that! My understanding of the 'this' object is that it's set based on the call-site of the function and falls under one of 4 rules: default binding, implicit binding, explicit binding, and "new"/constructor binding. It could also fall under more than one rule, then the order of precedence will apply. Learnt that from YDKJS.

And it might be good to provide some background by comparing 'this' between different programming languages:

In languages like Ruby or Java, this (or in case of Ruby self) will always point to the object in which your method is defined. So in Ruby if you are working on the foo method inside the Bar class, self will always point to the object which is the instance of the Bar class.
JavaScript works quite surprisingly here. Because in JavaScript function context is defined while calling the function, not while defining it! This is what can surprise many when coming to JS from different fields. Such late binding is a powerful mechanism which allows us to re-use loosely coupled functions in variety of contexts.
reactkungfu.com/2015/07/why-and-ho...

Looking forward to your article!

Wow! That's well written :-) I'm from a Java background. For a long time, I detested learning JS primarily because I felt the language had too many loopholes and Typescript was sorta the answer to most of the language goofups that happened. Also, I would say until I moved to mgmt I viewed one language as a holy grail of things. But when I did become an engineering manager, I could see things in a much broader perspective. I understood that technology is an enabler for solving business problems and each piece of tech has their own set of strengths and weakness. It is for important for us to understand them and choose them wisely for the use case that needs to be solved. Unfortunately, I realised this almost a decade later. I cringe especially when people over-engineer things for the sake of learning new technologies. For e.g. I've seen several architects directly pitch in Kafka for a simple messaging system which even an Active MQ would suffice. The worst part is that they justify the decision by throwing the word scalability in the mix. Sorry for digressing. It is good to enjoy a good conversation after a long time. Thank you for your comments.I enjoyed reading them.

 

Thank you for writing this. As you said, there is no one else that I've seen who talks about the different phases of the execution context, and that helped a lot.

One thing, though. I was thrown off by the fruit/color example as the output in the snippet doesn't seem to match the description you give of it. The output in the snippet returns the complete sentences, like the variables fruit and color were actually read, while instead the description says the output will return undefined as value of those same variables.

Am I missing something crucial?

 

Hello there,

Apologies! My bad on the incorrect output in the code section. I usually code in the editor and ensure the output is valid. But, in this case, I added console.log in the end and wanted to showcase before and after and later changed my script in the main article, but forgot to update it in the code portion of the article.

I have now updated it to reflect the correct output.

I'm surprised how this wasn't brought to my notice earlier. Kudos to you! I'm glad that folks are actually reading my articles. (even the code sections) I'll ensure that I verify the next time I post an article.

Thank you once again. I'm going to extend the concepts and also explain Closures which is another famous interview question as well as something often confused with. Hoping to publish it by end of the day. Stay tuned!

 

Thank you for taking the time to read the comment and review your article! I'll be looking forward to your next posts!

 

Great article. True, most js devs are aware of hoisting but not the reason behind it.
Btw did you mean "do share it with your friends" ? Because I was totally going to 🙂

 

Thank you! I'm glad you enjoyed it..do check out the other ones. Especially, the history of ECMAScript. That one is probably my favorite, since many of them don't know about how it evolved.

 
 

I definitely won't share this with friends

 

Hahahahahaha! Well, it's your choice :-) But, I love your sarcasm. Have a nice weekend my friend :-)

 

Just following recommendations from the bottom of the article xD Have a great weekend too!