Masterbook/Chapter 2• 5 min read
View Raw Code on GitHub

What is hoisting in JavaScript

Hositing In JavaScript

  • When JavaScript executes your code, it first performs a setup step where it registers all the variables and functions it can find. This behavior is known as hoisting. Because of hoisting, some variables and functions seem to exist even before the line where they are defined. However, different declarations
    are hoisted in different ways.

  • Function declarations are available early and can be used anywhere in their scope. Variables declared using var are also hoisted but start with a value of undefined. Variables declared with let, const, or class are hoisted too, but they are left uninitialized. If you try to access them before their declaration line, JavaScript throws a ReferenceError. This situation is known as the Temporal Dead Zone (TDZ).

  • Think of JavaScript running your file in two passes inside an execution context. In the first pass, called the creation phase, JavaScript scans your code and sets up memory for all variables and functions. It does not execute any statements yet; it only prepares the environment. In the second pass, called the execution phase, JavaScript runs the code line by line and assigns actual values to the variables or executes functions.

  • During the creation phase, JavaScript performs the following steps.

  1. Function declaration
function greet() {
  console.log("hi");
}
  • The entire function is stored in memory during setup, which means you can call greet anywhere in the same scope, even before its line in the code.
  1. var variables
var a = 10;
  • The name a is created and initialized with undefined during the setup phase. The actual assignment (= 10) happens later in the execution phase.
  1. let, const, and class
let x = 1;
const y = 2;
class Person {}
  • These names are created but left uninitialized. Any attempt to read them before their declaration line results in a ReferenceError. This waiting period is known as the Temporal Dead Zone.

  • When the execution phase begins, JavaScript runs your code from top to bottom. When it reaches var a = 10, it assigns 10 to the already existing a. When it reaches let x = 1 or const y = 2, it initializes them for the first time, exiting the TDZ. Function declarations were already ready to use, so they can be called before their declaration lines.

  • The TDZ exists to prevent you from using variables before they are properly defined. Without it, many confusing bugs would occur where variables show undefined values unexpectedly.

  • Here are some examples that show how hoisting works in different scenarios.

  • Example 1: var vs let and const


console.log(a1); // undefined (hoisted name + default value)
var a1 = 10;

console.log(b1); // ReferenceError (TDZ)
let b1 = 20;

console.log(c1); // ReferenceError (TDZ)
const c1 = 30;
  • Example 2: Function declaration vs function expression vs arrow function
sayHi(); // works (function declaration is hoisted fully)
function sayHi() {
  console.log("Hi!");
}

try {
  sayHello();
} catch (e) {
  console.log(e.name);
} // TypeError or ReferenceError
var sayHello = function () {
  console.log("Hello!");
};

try {
  wave();
} catch (e) {
  console.log(e.name);
} // ReferenceError (TDZ)
let wave = () => console.log("Wave!");
  • With var, the variable sayHello exists during setup and has the value undefined until its assignment line. Function expressions and arrow functions behave like variable assignments and are not ready until the line is executed. With let or const, the variable exists but is uninitialized until that line is reached.

  • Example 3: class behaves like let and const (TDZ)

try {
  new Person();
} catch (e) {
  console.log(e.name);
} // ReferenceError (TDZ)
class Person { }
  • To visualize how hoisting works, imagine time flowing from top to bottom in your code.

CREATION PHASE (Setup, before running your lines)

  • function greet -> ready (you can call it)
  • var a -> exists with value undefined
  • let b -> exists but uninitialized (TDZ)
  • const c -> exists but uninitialized (TDZ)

EXECUTION PHASE (Run your lines)

  1. console.log(a) -> prints undefined
  2. var a = 10 -> assigns 10 to a
  3. console.log(b) -> ReferenceError (still in TDZ)
  4. let b = 20 -> initializes b to 20 (TDZ ends)
  5. greet() -> works (declaration was ready)
  6. const c = 30 -> initializes c to 30 (TDZ ends)
  • There are several common misconceptions about hoisting. Some people think let and const are not hoisted. In reality, they are hoisted, but they remain uninitialized until their declaration line. Others think hoisting moves code to the top. That is not true; the JavaScript engine only creates memory bindings for variables and functions during setup. Another misconception is that function expressions are hoisted like function declarations. Only function declarations are hoisted with their body ready. Function expressions or arrow functions depend on whether the variable is declared using var, let, or const. Finally, var is sometimes used in loops with asynchronous code, which often causes bugs because all iterations share the same variable. It is better to use let in such cases.

  • There are also some subtle edge cases you may encounter.

  • Redeclaring with var

var x1 = 1;
var x1 = 2; // allowed
console.log(x); // 2

/*

  • var allows redeclaration in the same scope, which can lead to unexpected results.

  • TDZ with default parameters referencing later bindings
    */