JavaScript Hoisting Explained — var, let, const, and Functions | TryJS

Understand JavaScript hoisting with runnable examples. How var, let, const, and function declarations are hoisted and what the temporal dead zone means.

Overview

Hoisting is the JavaScript engine's practice of allocating declarations before executing code in a scope. Function declarations are fully hoisted — you can call them before their line of source. var declarations are hoisted but initialized to undefined, so reading them before the line works but gives undefined. let and const are hoisted too, but placed in a 'temporal dead zone' where reading them throws until execution reaches the line. Understanding hoisting is essential for reading unfamiliar code and avoiding classic 'undefined' bugs.

How It Works

Function declarations: fully hoisted

function foo() {} declarations are moved to the top of their scope with their body intact. You can call foo() on the first line even if the definition is at the bottom.

var: hoisted, initialized to undefined

var x = 1 is split into two steps: the declaration var x moves to the top (initialized to undefined), and the assignment x = 1 stays put. Reading x before the assignment line returns undefined.

let and const: hoisted but in the TDZ

let and const declarations are hoisted, but the variable is in the 'temporal dead zone' (TDZ) from the start of the scope until the declaration line. Reading it throws a ReferenceError.

Function expressions are NOT hoisted

const foo = function() {} follows the const hoisting rules, not function declaration rules. Calling foo() before the assignment throws, not returns undefined.

Common Mistakes

When to Use It

You can't opt out of hoisting, but you can avoid its pitfalls: always prefer let and const over var (the TDZ forces you to declare-before-use), and declare functions near the top of their scope so readers don't have to rely on hoisting to follow the code.

Runnable Example

// Function declarations are fully hoisted
console.log(add(2, 3)); // 5
function add(a, b) {
  return a + b;
}

// var is hoisted and initialized to undefined
console.log(typeof x); // "undefined" (no error)
var x = 10;

// let is hoisted but in the TDZ
try {
  console.log(y);
} catch (e) {
  console.log("TDZ error:", e.message);
}
let y = 20;

// Function expression — NOT hoisted
try {
  greet();
} catch (e) {
  console.log("expression error:", e.message);
}
const greet = function() {
  return "hi";
};

Open this example in the TryJS playground to edit and run the code instantly in your browser — no signup needed.

Frequently Asked Questions

What is hoisting in JavaScript?

Hoisting is the conceptual model where variable and function declarations are processed before any code runs in a scope. Functions are fully hoisted; var is hoisted and initialized to undefined; let and const are hoisted but unusable until their declaration line is reached.

Are let and const hoisted?

Yes — but accessing them before the declaration line throws a ReferenceError because they're in the 'temporal dead zone'. This is different from var, which returns undefined when read before its line.

What is the temporal dead zone?

The region of a scope from the top down to a let or const declaration, where the variable exists but can't be read or written. Any attempt throws a ReferenceError. It ends as soon as execution reaches the declaration.

Why are function declarations hoisted but function expressions aren't?

Function declarations are parsed and added to the scope in full before execution starts. Function expressions are regular assignments to a variable — the variable is hoisted (as var/let/const) but the function value isn't bound until the assignment runs.