Hoisting in JavaScript


Hoisting is one of the more unique features of JavaScript that can sometimes confuse beginners. It refers to the process by which the JavaScript engine moves variable and function declarations to the top of their containing scope (function or global). This behaviour allows you to use functions and variables before they are formally declared in the code.

Hoisting in JavaScript

In this article, we'll explore what hoisting is, how it affects variable and function declarations, and how var, let, and const behave in relation to hoisting.


Hoisting

In JavaScript, hoisting is a default behaviour where declarations of variables and functions are moved to the top of their containing scope (either global scope or function scope) during the compilation phase, before the code is executed. However, only the declarations are hoisted, not the initializations.

Essentially, when JavaScript runs, it looks at your entire code and pulls all declarations (but not their assignments) to the top, regardless of where they appear in the code.

Example:

console.log(x);  // Output: undefined
var x = 5;

In this example, the declaration of x (var x;) is hoisted to the top, but its initialization (x = 5;) remains in place. This is why undefined is printed instead of 5.

Internally, JavaScript interprets the code like this:

var x;
console.log(x);  // Output: undefined
x = 5;

Hoisting with var

The var keyword is hoisted to the top of its scope. However, only the declaration is hoisted, not the initialization.

Example:

function hoistVar() {
  console.log(a);  // Output: undefined
  var a = 10;
  console.log(a);  // Output: 10
}

hoistVar();

Internally, this is interpreted by JavaScript as:

function hoistVar() {
  var a;
  console.log(a);  // Output: undefined
  a = 10;
  console.log(a);  // Output: 10
}

hoistVar();

In the first console.log(a);, a is undefined because it has been hoisted but not yet assigned a value.


Hoisting with let and const

Unlike var, variables declared with let and const are also hoisted, but they are not initialized until the point of actual declaration in the code. If you try to access them before the declaration, you will get a ReferenceError. This period between the start of the scope and the actual declaration is known as the temporal dead zone.

Example with let:

console.log(b);  // ReferenceError: Cannot access 'b' before initialization
let b = 20;

In this case, JavaScript does not initialize the variable b during hoisting, so trying to access it before its declaration causes an error.

Example with const:

console.log(c);  // ReferenceError: Cannot access 'c' before initialization
const c = 30;

Like let, variables declared with const also have a temporal dead zone and result in a ReferenceError if accessed before declaration.


Function Hoisting

Function declarations in JavaScript are fully hoisted. This means that you can call a function before its declaration in the code, and it will work as expected.

Example:

greet();  // Output: Hello!

function greet() {
  console.log('Hello!');
}

Even though greet() is called before the function is defined, it works because the function declaration is hoisted to the top of the scope.

Internally, the JavaScript engine treats the code like this:

function greet() {
  console.log('Hello!');
}

greet();  // Output: Hello!

Function Expressions and Hoisting

Function expressions (where you assign a function to a variable) are treated differently. Since variables declared with var, let, or const are hoisted but not initialized, function expressions are not hoisted in the same way as function declarations.

Example with var:

console.log(sayHello);  // Output: undefined
var sayHello = function() {
  console.log('Hi!');
};

In this case, sayHello is hoisted, but the function itself is not initialized until after the console.log, so the result is undefined.

Example with let or const:

sayHello();  // ReferenceError: Cannot access 'sayHello' before initialization
let sayHello = function() {
  console.log('Hi!');
};

Here, sayHello is in the temporal dead zone until the let declaration is encountered, leading to a ReferenceError when accessed before initialization.


Best Practices to Avoid Hoisting Issues

  1. Declare variables at the top: Even though hoisting allows variables to be used before they are declared, it is good practice to declare variables at the beginning of their scope (block, function, etc.). This avoids confusion and makes the code more readable.
  2. Use let and const instead of var: Since var can lead to unexpected results due to hoisting, it is generally recommended to use let and const for variable declarations. These provide more predictable scoping rules and help prevent issues with hoisting.
  3. Avoid relying on function hoisting: While function hoisting allows functions to be called before their definition, it can make the code harder to understand. Consider defining functions before they are used to make the flow of your code clearer.

Hoisting Summary

  • Variables declared with var are hoisted and initialized with undefined. Accessing a var variable before declaration will result in undefined.
  • Variables declared with let and const are hoisted, but they are not initialized until the point of their declaration. Accessing them before declaration results in a ReferenceError due to the temporal dead zone.
  • Function declarations are fully hoisted, so they can be called before they are defined.
  • Function expressions, whether declared with var, let, or const, are not hoisted in the same way as function declarations.