Hoisting & Temporal Dead Zone

Omkar PatilOmkar Patil
6 min read

1. Variable Hoisting

Hoisting is JavaScript’s default behavior of moving variable and function declarations to the top of their containing scope (global or function scope) during the compilation phase, before the code is executed. However, only declarations are hoisted, not initializations or assignments.

a) var Hoisting

  • Variables declared with var are hoisted to the top of their scope and initialized with undefined.

  • You can access a var variable before its declaration, but it will return undefined because the initialization (assignment) stays in place.

Example:

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

How it works internally: JavaScript moves the declaration to the top, so the code is interpreted as:

var x; // Declaration hoisted, initialized to undefined
console.log(x); // undefined
x = 5; // Assignment stays in place
console.log(x); // 5

b) let and const Hoisting

  • Variables declared with let and const are also hoisted, but they are not initialized until their declaration line is reached in the code.

  • This creates a Temporal Dead Zone (TDZ) (explained below) where accessing the variable before its declaration throws a ReferenceError.

  • Unlike var, let and const are block-scoped, not function-scoped.

Example:

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

Key Difference:

  • var: Hoisted and initialized with undefined.

  • let/const: Hoisted but not initialized, leading to TDZ.

2. Function Hoisting

Function declarations are fully hoisted, meaning both the declaration and the function body are moved to the top of the scope. This allows you to call a function before its declaration in the code.

a) Function Declarations

sayHello(); // Output: "Hello!"
function sayHello() {
  console.log("Hello!");
}

How it works:

  • The entire function sayHello is hoisted, so it’s available anywhere in its scope, even before the declaration.

b) Function Expressions

  • Function expressions (where a function is assigned to a variable) are treated differently based on the variable’s declaration type (var, let, or const).

  • The variable declaration is hoisted, but the function assignment is not.

Example with var:

console.log(myFunc); // Output: undefined
myFunc(); // TypeError: myFunc is not a function
var myFunc = function() {
  console.log("Hi!");
};

How it works:

  • The var myFunc declaration is hoisted and initialized to undefined.

  • The assignment myFunc = function() {...} stays in place.

  • Trying to call myFunc() before the assignment throws a TypeError because undefined is not a function.

Example with let/const:

console.log(myFunc); // ReferenceError: Cannot access 'myFunc' before initialization
let myFunc = function() {
  console.log("Hi!");
};
  • The let myFunc declaration is hoisted but not initialized (TDZ applies).

3. Temporal Dead Zone (TDZ)

The Temporal Dead Zone is the period between the start of a block scope and the point where a let or const variable is declared. During this time, accessing the variable results in a ReferenceError.

Why TDZ Exists

  • let and const were introduced in ES6 to provide block-scoping and prevent unsafe access to variables before they are initialized.

  • The TDZ enforces that you cannot use a variable until its declaration is reached, making code behavior more predictable.

Example:

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

TDZ Explanation:

  • The variable a is hoisted to the top of the block scope but remains uninitialized until the let a = 20 line is executed.

  • Any attempt to access a before this line throws a ReferenceError.

Key Points about TDZ:

  • Applies to let and const variables (not var).

  • Starts at the beginning of the block scope and ends when the variable is declared.

  • Even checking typeof on a TDZ variable throws a ReferenceError:

      console.log(typeof x); // ReferenceError: Cannot access 'x' before initialization
      let x = 10;
    

Let’s debug five code snippets to understand hoisting and TDZ in action. For each, I’ll explain the issue, predict the output, and provide a fix (if applicable).

Snippet 1

console.log(a);
var a = 10;
console.log(a);

Expected Output:

undefined
10

Explanation:

  • var a is hoisted to the top and initialized with undefined.

  • The first console.log(a) outputs undefined.

  • The assignment a = 10 happens, so the second console.log(a) outputs 10.

  • No error because var variables are initialized during hoisting.

Fix (if you want to avoid accessing before declaration):

  • Move the declaration before the first console.log:

      var a = 10;
      console.log(a); // 10
      console.log(a); // 10
    

Snippet 2

console.log(b);
let b = 20;

Expected Output:

ReferenceError: Cannot access 'b' before initialization

Explanation:

  • let b is hoisted to the top of the block scope but is not initialized (TDZ).

  • Accessing b before its declaration throws a ReferenceError due to the TDZ.

Fix:

  • Declare and initialize b before accessing it:

      let b = 20;
      console.log(b); // 20
    

Snippet 3

greet(); // Works fine
function greet() {
  console.log("Hello, World!");
}

Expected Output:

Hello, World!

Explanation:

  • Function declarations are fully hoisted, including their body.

  • The greet function is available before its declaration, so calling greet() works.

  • No issue to fix here; this is expected behavior.

Snippet 4

console.log(myFunc);
var myFunc = function() {
  console.log("Function Expression");
};
myFunc();

Expected Output:

undefined
Function Expression

Explanation:

  • var myFunc is hoisted and initialized to undefined.

  • The first console.log(myFunc) outputs undefined.

  • The assignment myFunc = function() {...} happens, so myFunc() calls the function and outputs "Function Expression".

  • If you tried myFunc() before the assignment, it would throw a TypeError: myFunc is not a function.

Fix (to avoid accessing before assignment):

  • Declare and assign the function expression before logging:

      var myFunc = function() {
        console.log("Function Expression");
      };
      console.log(myFunc); // [Function: myFunc]
      myFunc(); // Function Expression
    

Snippet 5

{
  console.log(c);
  const c = 30;
  console.log(c);
}

Expected Output:

ReferenceError: Cannot access 'c' before initialization

Explanation:

  • const c is hoisted to the top of the block scope but is in the TDZ until its declaration.

  • The first console.log(c) throws a ReferenceError because c is accessed before initialization.

  • The second console.log(c) is never reached due to the error.

Fix:

  • Declare and initialize const c before accessing it:

      {
        const c = 30;
        console.log(c); // 30
        console.log(c); // 30
      }
    

Key Takeaways

  1. Hoisting:

    • var: Hoisted and initialized to undefined.

    • let/const: Hoisted but not initialized (leads to TDZ).

    • Function declarations: Fully hoisted (declaration + body).

    • Function expressions: Only the variable declaration is hoisted, not the assignment.

  2. Temporal Dead Zone:

    • Applies to let and const.

    • From the start of the block scope to the declaration line, accessing the variable throws a ReferenceError.

  3. Best Practices:

    • Declare variables (var, let, const) at the top of their scope to avoid hoisting-related surprises.

    • Use let or const over var for block-scoping and to leverage TDZ for safer code.

    • Avoid calling functions or accessing variables before their declaration/assignment.


Additional Notes

  • Why TDZ is useful: It catches bugs early by preventing access to uninitialized variables, unlike var which silently returns undefined.

  • Hoisting in strict mode: Using "use strict" doesn’t change hoisting behavior but can prevent other sloppy practices (e.g., using undeclared variables).

  • Real-world debugging: Hoisting issues often arise in large codebases where variable/function usage isn’t clearly ordered. Always initialize variables before use.

0
Subscribe to my newsletter

Read articles from Omkar Patil directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Omkar Patil
Omkar Patil