loading...

Understanding Hoisting in JavaScript

imwiss profile image Wissam Abirached Originally published at designingforscale.com ・5 min read

You know JavaScript, but do you really know JavaScript? It's a great language, even though some may argue otherwise. Sure, it's got some bad parts, but it has improved a lot in past years and developers are getting much better at using JavaScript correctly and at following best practices. Strict mode is also getting better at preventing newer developers from making some bad JavaScript mistakes and unfortunately running into unwanted behaviours.

However, not everyone has heard of the term Hoisting or knows what it means. In this article, I'll explain what hoisting is and show different examples so that you can better understand what it's all about.

Learning

The JavaScript Interpreter

When you execute your JavaScript code, the interpreter goes through the code twice.

The first run through the code is where it does a safety check and small optimizations of your code. Safety checks such as making sure that the syntax is right, if there are any calls to eval or with, etc. Then, it optimizes the code as best as it can to ensure better performance when it is executed. This is also where hoisting occurs (more on this soon), and is referred to as the compile run.

The second run is where it actually executes your code by going through it line by line, doing the assignments, calling the functions, and so on.

What is Hoisting?

Hoisting is when the JavaScript interpreter moves all variable and function declarations to the top of the current scope. It's important to keep in mind that only the actual declarations are hoisted, and that assignments are left where they are.

Hoisting is done during the interpreter's first run through the code.

Variable Declarations

Let's start with a basic example and look at the following code:

'use strict';

console.log(bar); // undefined
var bar = 'bar';
console.log(bar); // 'bar'

At first, you may think that the sample code would throw a ReferenceError on line 3 (console.log(bar);) because bar has not been declared yet. However, with the magic of hoisting, it won't throw a ReferenceError but the value of bar will be undefined at that point. This is because the JavaScript interpreter does a first run through the whole code and declares all variables and functions at the top of the current scope, and then, on the second run, will execute the code.

Here's what the same code would look like after the interpreter's first run:

'use strict';
var bar;
console.log(bar); // undefined
bar = 'bar';
console.log(bar); // 'bar'

Notice how bar is now declared at the top (var bar) but is not yet assigned at that point? It's a subtle but important difference, and this is why bar is logged as undefined instead of throwing a ReferenceError.

Function Declarations

Hoisting also applies to function declarations (not function expressions). Let's analyze the following sample code:

'use strict';

foo();
function foo() {
    console.log(bam); // undefined
    var bam = 'bam';
}

console.log(bam); // ReferenceError: bam is not defined

In this sample code, we are able to successfully call the function foo since it's a function declaration and therefore it is hoisted as-is to the top of the current scope. Then, foo will output undefined when calling it since, as in the previous example, bam is hoisted to the top of its current scope, which is function foo(). This means that bam was declared before calling console.log(bam) but it has not yet been assigned a value (bam = 'bam').

However, the important thing to note here is that bam was hoisted at the top of its current scope. This means that it was not declared in the global scope but in the function's scope instead.

Here's what the same code would look like after the interpreter's first run:

'use strict';

function foo() {
    var bam;
    console.log(bam); // undefined
    bam = 'bam';
}

foo();
console.log(bam); // ReferenceError: bam is not defined

Notice how foo() was moved to the top, and bam is declared in foo()? This means that, when you call console.log(bam) on line 10, it will not find the variable bam in the general scope and will throw a ReferenceError.

Function Expressions

Next, the third use case I'd like to cover is how function expressions are not hoisted as opposed to function declarations. Instead, it's their variable declarations that are hoisted. Here's some sample code to demonstrate my point:

'use strict';

foo();
var foo = function () {
    console.log(bam); // undefined
    var bam = 'bam';
}

This code throws a TypeError: foo is not a function error since only the variable declaration var foo is hoisted to the top of the file, and the assignment of the function to foo is done on the interpreter's second run only.

Here's what the same code would look like after the interpreter's first run:

'use strict';

var foo;
foo(); // `foo` has not been assigned the function yet
foo = function () {
    console.log(bam);
    var bam = 'bam';
}

What Takes Precedence?

Finally, the last use case I'd like to cover is that function declarations are hoisted before variables. Let's look at the following code:

'use strict';

console.log(typeof foo); // 'function'

var foo = 'foo';

function foo () {
    var bam = 'bam';
    console.log(bam);
}

In this example, typeof foo returns function instead of string, even though the function foo() is declared after the variable. This is because function declarations are hoisted before variable declarations, so foo = 'foo' is executed on the second run, after we call typeof foo.

On the first run, the interpreter will hoist foo() at the top of the current scope, and then will get to the var foo = 'foo' line. At that point, it realizes that foo was already declared so it doesn't need to do anything and will continue its first run through the code.

Then, on the second run (which basically executes the code), it'll call typeof foo before it gets to the assignment foo = 'foo'.

Here's what the same code would look like after the interpreter's first run:

'use strict';

function foo () {
    var bam = 'bam';
    console.log(bam);
}

console.log(typeof foo); // 'function'
foo = 'foo';

ES6

ES6 is the future and is what most developers will be using moving forward, so let's see how hoisting applies for ES6 code.

Hoisting doesn't apply the same way for let and const variables compared to var variables, as we saw above. However, let and const variables are still hoisted, the difference being that they cannot be accessed until the assignment is done at runtime.

From ES6's documentation:

The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated.

At the end of the day, it's a small technicality where the interpreter applies hoisting to these variables on the compile run but they'll throw reference errors when accessed before the assignment happens, essentially preventing us from accessing these variables before their assignment.

Conclusion

I hope this clarifies how hoisting works in JavaScript. It's definitely not as tricky or complicated as it sounds, but it does require us to breakdown the different use cases and trying different scenarios to understand how things work under the hood.

Do not hesitate to leave me comments or questions if you have any - I'd love to hear your feedback.

This post was originally published on Designing for Scale

Posted on by:

imwiss profile

Wissam Abirached

@imwiss

Senior Engineering Manager at GitHub, building APIs. Passionate about fostering strong engineering cultures in distributed organizations. Husband and dad of 4.

Discussion

markdown guide
 

The most important thing to understand about hoisting is that let and const don't have it. It's 2017, unless you are targeting very old browsers or working is some legacy code base, you should not use var ever again.

 

Thanks for the feedback Daniel!

That's true, ES6 is the future and is what most developers will be using moving forward. That said, it's not entirely true that hoisting doesn't apply to let and const variables. It doesn't apply the same way as for var, but these let and const variables are still hoisted. The difference though is that they cannot be accessed until the assignment is done at runtime.

From ES6's documentation:

The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated.

At the end of the day, it's a small technicality where the interpreter applies hoisting to these variables on the compile run but they'll throw reference errors when accessed before the assignment happens, so your point is right :)

I'll update my post, thanks!

 

This is good stuff! Hoisting is definitely one of those language aspects that needs explanation.

I didn't have any hint that it was a thing besides seemingly inconsistent errors, but learned the details because of Douglas Crockford's opinionated JSLint tool telling me I needed to put my var declarations at the start of my functions.

 

Thanks Weston!

I agree that it's a very important part to understand, otherwise we won't understand why our code behaves certain ways on runtime. Douglas Crockford is definitely one of the best JS experts out there, along with Kyle Simpson in my opinion. I learned so much from the both of them.

 

Most excellent. Clear. Concise. Logical examples. Great work, thanks!

 

Thanks, appreciate that! Glad you enjoyed it.

 

That took some getting my head around but I understand it, thank you. A good nugget of knowledge worth remembering should a situation ever arise!

 

Thanks Andrew! Glad you found it helpful.

 

Fantastic article - thank you! I vaguely understood there was hoisting, but the use cases make the differences really clear.

 

Awesome, so glad you found it helpful!

 
 

Awesome, glad it was helpful! Thanks Anton.

 

This was great! Beginner friendly and clear explanation. I will share this with my students. Thanks for this.

 
 

Thanks for the great explanation. I'm new to JavaScript and this helps!

 
 

This is great. Hoisting is an issue I ran into just yesterday.

Good to know the order in which things are hoisted. very useful.

 
 
 

Nice ! I didn't know about it

 

Thanks, glad it was helpful!