Skip to main content

Command Palette

Search for a command to run...

JavaScript Temporal Dead Zone and Variable Declarations

Updated
4 min read

Part 3 of the JavaScript Deep Dive Series

In our previous article, we learned that JavaScript has a compilation phase where it scans through your code and sets up variable declarations. But not all variable declarations behave the same way during this process.

Understanding the differences between var, let, and const - and the "Temporal Dead Zone" is important for writing predictable JavaScript and avoiding common bugs.

Definitions

Before we proceed, let's explain a few terms you will come accross:

Temporal Dead Zone (TDZ) - The period between when a variable is hoisted (during compilation) and when it's initialized with a value. During this time, accessing the variable throws a ReferenceError.

Block Scope - A scope created by curly braces {}. Variables declared with let and const are confined to the block where they're declared.

Function Scope - A scope created by functions. Variables declared with var are confined to the function where they're declared (or global if outside any function).

Hoisting - The process during compilation where variable and function declarations are processed before code execution begins.

Initialization - The moment a variable gets its first value assignment.

The Three Variable Declarations

JavaScript gives you three ways to declare variables, each with different scoping and hoisting behavior:

var teacher = "Kyle";     // Function-scoped, undefined when hoisted
let student = "Frank";    // Block-scoped, TDZ when hoisted  
const course = "JS";      // Block-scoped, TDZ when hoisted, immutable

VAR

var declarations are function-scoped and get initialized with undefined during the compilation phase:

console.log(teacher);     // undefined (not an error!)
var teacher = "Kyle";
console.log(teacher);     // "Kyle"

What happens:

  1. Compilation: teacher is declared in function scope, initialized with undefined

  2. Execution: First console.log prints undefined, then assignment happens

LET and CONST - The Modern Approach

let and const are block-scoped and exist in the Temporal Dead Zone until their declaration line:

console.log(student);     // ReferenceError!
let student = "Frank";
console.log(student);     // "Frank"

What happens:

  1. Compilation: student is declared in block scope but NOT initialized

  2. Execution: First console.log tries to access uninitialized variable → ReferenceError

Block Scope in Action

Block scope means variables are confined to the nearest set of curly braces:

var globalVar = "I'm global";

if (true) {
    var functionScoped = "I escape the block";
    let blockScoped = "I'm trapped in the block";
    const alsoBlocked = "Me too";
}

console.log(functionScoped);  // "I escape the block"
console.log(blockScoped);     // ReferenceError!
console.log(alsoBlocked);     // ReferenceError!

The difference:

  • var ignores block boundaries (function-scoped)

  • let and const respect block boundaries (block-scoped)

Temporal Dead Zone (TDZ)

The TDZ is the time between variable hoisting and initialization:

console.log("Before declaration");

console.log(a);  // ReferenceError - TDZ violation
console.log(b);  // undefined - no TDZ for var

let a = "Hello";
var b = "World";

console.log("After declaration");
console.log(a);  // "Hello"  
console.log(b);  // "World"

TDZ Timeline:

  1. Compilation: Both a and b are hoisted

  2. Execution starts: a is in TDZ, b is undefined

  3. Line 3: Accessing a → ReferenceError (TDZ violation)

  4. Line 4: Accessing bundefined (allowed)

  5. Line 6: a exits TDZ, gets value "Hello"

  6. Line 9: Both variables accessible normally

Practical Implications

Loop Behavior

// var - function scoped, same variable
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);  // 3, 3, 3
}

// let - block scoped, new variable each iteration  
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);  // 0, 1, 2
}

Const Immutability

const user = { name: "Kyle" };
user.name = "Frank";        // OK - modifying property
console.log(user.name);     // "Frank"

user = { name: "Suzy" };    // Error - reassigning const

Block Scope Benefits

function processData() {
    if (condition) {
        let tempData = calculateSomething();
        let result = process(tempData);
        return result;
        // tempData and result die here
    }
    // tempData and result not accessible here
}

Common Misconceptions

"let and const aren't hoisted" False. They are hoisted but remain uninitialized in the TDZ.

"const means immutable"
Partially false. The binding is immutable, but object/array contents can change.

"var is always bad" False. var has legitimate use cases, especially for function-scoped variables.

"Block scope is just for loops" False. Any {} creates block scope for let/const.

When to Use Which

Use const by default - Prevents accidental reassignment

const API_URL = "https://api.example.com";
const users = [];  // Can still push to array

Use let when you need reassignment - Clear signal of mutability

let counter = 0;
let currentUser = null;

Use var sparingly - Only when you specifically need function scope

function processItems() {
    for (var i = 0; i < items.length; i++) {
        // var i is function-scoped, accessible after loop
    }
    return i;  // Final value of i
}

Summary: Choose Your Declaration Wisely

Understanding variable declarations is fundamental to JavaScript mastery:

Compilation vs Execution: All declarations are hoisted, but initialized differently TDZ Protection: let/const prevent access before initialization
Scope Boundaries: var ignores blocks, let/const respect them Best Practices: Prefer const, use let for reassignment, avoid var unless necessary

This knowledge sets the foundation for understanding:

  • Why certain bugs occur during development

  • How closures capture variables correctly

  • Module patterns and encapsulation strategies

  • Modern JavaScript best practices

Javascript Deep dive

Part 2 of 4

A journey through JavaScript's inner workings, one concept at a time. This series goes beyond syntax and into the heart of how JavaScript actually works. We'll explore the engine, the call stack, memory management, and other advanced concepts.

Up next

JavaScript Lexical Scope and Compilation

Part 2 of the JavaScript Deep Dive Series Before JavaScript executes a single line of your code, something crucial happens behind the scenes. While most developers think JavaScript executes and runs code line by line(I know I mentioned this in the fi...

More from this blog

C

Cracked Chefs by Oluwaferanmi Adeniji

19 posts

Battle-tested Coding patterns, Javascript wizardry, System Design, Product Engineering and Management, and architectural secrets.