Hoisting in JavaScript

Hoisting refers to visibility of a variable at the beginning of the scope even though its declaration may happen further down in the scope. So, even though the declaration of a variable or a function is not at the beginning of the scope but somewhere down the line, it will be automatically hoisted at the top as if the variable was indeed declared at the top of the scope. Now, important to note here, only variable declaration gets hoisted, not the value. So, the hoisted variable (in case of var) will contain default value (which is undefined) till something gets assigned to it.

There are two things that get hoisted, functions and var variables. Let’s first start with function hoisting.

Function Hoisting

Consider the following code:

fun();
// Hello World

function fun() {
console.log("Hello World");
}

According to the hoisting principle, fun identifier is visible from the beginning itself. But can we call fun before it is declared? The answer is yes. But, wait, in the beginning I said, just the declaration gets hoisted and not its value! Well for function declarations, the identifier as well as the reference to the function gets hoisted up. So, the entire function is available at the beginning of the scope and hence we can call it.

Now consider the following code:

var fun = function() {
console.log("Hello World");
};

This is just another way of defining a function in JS (called function expression). But, now, if fun is called before this fun assignment, then what will happen? It will give type error!

greeting();
// TypeError

var greeting = function greeting() {
console.log("Hello!");
};

This kind of hoisting is like variable hoisting. The identifier is available at the beginning of the scope but it gets a value only when the assignment statement is encountered. Till then it will have undefined value. And calling undefined() leads to Type Error.

Notice that the error is not Reference Error, that means greeting identifier was found. But, it does not hold the function reference yet. It will be undefined at that moment.

Variable Hoisting

console.log(greeting);
// undefined

var greeting = "Howdy!";
console.log(greeting);
// Howdy!

Variables declared with var are hoisted at the top of their scope and are auto initialized to undefined. Hence, the first line prints undefined. Then the variable actually gets a value at line 2. And hence the next line prints “Howdy!”.

Now consider this:

greeting = "Hello!";
console.log(greeting);
// Hello!

var greeting = "Howdy!";
console.log(greeting);
// Howdy!

What is happening here?

  1. variable greeting is hoisted and is auto initialized to undefined.
  2. greeting gets value “Hello!”
  3. Prints “Hello!”
  4. greeting gets value “Howdy!”
  5. Prints “Howdy!”

What happens when we re-declare the variable in the same scope?

Consider this:

var studentName = "Frank";
console.log(studentName);
// Frank

var studentName;
console.log(studentName); // ???

What does the last line prints? undefined? The answer is no.

How does hoisting work here? Considering that the declaration move up during hoisting process, let’s rearrange this code:

var studentName;
var studentName; // pointless operation

studentName = "Frank";
console.log(studentName);
// Frank

console.log(studentName);
// Frank

Second var studentName here is a pointless statement and does not do anything!

Hoisting with let and const

Do let and const also get hoisted? The answer is yes. But there is a twist. They do get hoisted at the beginning of their scope but unlike var, when hoisted, they are not auto initialized to undefined. Let’s see an example.

console.log(studentName);
// ReferenceError

let studentName = "Suzy";

Since, studentName remains uninitialized until line 2, when we try to access it at line 1, we get an error. Now, let’s try to initialize this uninitialized studentName , consider this:

studentName = "Suzy";   // let's try to initialize it!
// ReferenceError

console.log(studentName);

let studentName;

Now, studentName gets hoisted at top and it gets initialized at line 1. Then, why do we get Reference error again?

studentName does get hoisted, but, how do we initialize an uninitialized variable? For let/const, the only way to do so is with an assignment attached to a declaration statement. An assignment by itself is insufficient! So in above code, declaration and assignment should be combined. It should be written like this:

let studentName = "Suzy";
console.log(studentName); // Suzy

Alternatively:

let studentName;

studentName = "Suzy";

console.log(studentName);
// Suzy

From the above examples it is evident that we cannot use the variable at any point prior to the initialization of that variable. In previous snippet where we were getting Reference Error, studentName does get hoisted but remains uninitialized. That means it does not have any value, not even undefined. It gets initialized only later. Till that time it is not available and this period of unavailability is called Temporal Dead Zone (TDZ). In more formal language, TDZ refers to the period of time from the entering of a scope to where the auto-initialization of the variable occurs. A var also has TDZ but it is zero in length as it gets auto initialized to undefined as soon as it is declared. let and const have observable TDZ. And if you try to access the variable in between TDZ, you will get error. As a good coding practice, you should always declare your variables before using them and preferably this should be done at the top of the scope.