Understanding JavaScript Hoisting for Beginners

Hoisting is JavaScript's behavior of moving declarations to the top of their scope. Learn how it works for var, let, const, and function declarations -- and the bugs it can cause.

6 min read

Hoisting is JavaScript's behavior of making declarations available before the line where they appear in the code. The engine processes declarations first, then executes the code. The result acts as if declarations were physically moved to the top of their scope.

javascriptjavascript
console.log(greet("Alex")); // "Hello, Alex!"
 
function greet(name) {
  return `Hello, ${name}!`;
}

The function is called before its definition line, but it works. That is function hoisting. The entire function -- name and body -- is available from the top of the scope.

How Hoisting Actually Works

JavaScript runs code in two phases:

  1. Creation phase: the engine scans for declarations and registers them in memory.
  2. Execution phase: the engine runs the code line by line.

Hoisting is the visible result of phase 1. Declarations are registered before any code runs, so they seem to be "moved up."

Function Declarations: Full Hoisting

Function declarations are hoisted completely. Both the name and the body are available before the definition:

javascriptjavascript
console.log(double(4)); // 8
 
function double(n) {
  return n * 2;
}

This is the only declaration type where you can use the value before the line. It is why many developers order their code with helper functions at the bottom and the main logic at the top.

var: Hoisted but undefined

var declarations are hoisted, but only the declaration, not the assignment:

javascriptjavascript
console.log(name); // undefined (not ReferenceError!)
var name = "Alex";
console.log(name); // "Alex"

Mentally, it helps to expand what the engine actually does during the creation phase. What JavaScript effectively sees is closer to this, with the declaration split apart from the assignment:

javascriptjavascript
var name;            // Hoisted declaration, initialized to undefined
console.log(name);   // undefined
name = "Alex";       // Assignment stays at the original line
console.log(name);   // "Alex"

The variable exists from the top but holds undefined until the assignment line runs. This is why var can cause subtle bugs, accessing a var before its assignment does not throw an error, it silently returns undefined.

let and const: Hoisted but in the Temporal Dead Zone

let and const are hoisted but placed in the temporal dead zone (TDZ). You cannot access them before the declaration line at all:

javascriptjavascript
console.log(age); // ReferenceError: Cannot access 'age' before initialization
let age = 25;

The variable exists in the scope from the top, but JavaScript enforces a "no touching" rule until the declaration line executes. This is safer than var's silent undefined behavior, you get an immediate, clear error instead.

javascriptjavascript
// Temporal dead zone in action
const x = 1;
 
{
  console.log(x); // ReferenceError -- TDZ for the inner x
  const x = 2;    // This declaration "taints" the whole block
}

Even though there is an x in the outer scope, the inner const x declaration applies to the entire block. The console.log line is in the TDZ of the inner x, so it throws.

Function Expressions and Arrow Functions: Not Hoisted

Only the variable declaration is hoisted for function expressions and arrow functions. The function itself is assigned at runtime:

javascriptjavascript
console.log(typeof add); // "undefined" (with var) or ReferenceError (with let/const)
 
var add = function(a, b) {
  return a + b;
};
 
// Or with let/const:
console.log(typeof subtract); // ReferenceError
const subtract = (a, b) => a - b;

If you use var, the variable exists but is undefined, calling it throws a TypeError saying it is not a function. If you use let or const, accessing before the line throws a ReferenceError instead.

Hoisting Comparison

DeclarationHoisted?Usable Before Line?Value Before Line
function name() {}Yes, fullyYesThe function
var x = valueDeclaration onlyYes, but riskyundefined
let x = valueYes, but TDZNoReferenceError
const x = valueYes, but TDZNoReferenceError
const fn = function() {}Variable onlyNoReferenceError
const fn = () => {}Variable onlyNoReferenceError
class Name {}Yes, but TDZNoReferenceError

Common Hoisting Bug: var in Loops

javascriptjavascript
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Prints: 3, 3, 3 (not 0, 1, 2)

Because var is function-scoped, there is only one i variable. By the time the callbacks run, the loop has finished and i is 3. Using let fixes this because let creates a new binding per iteration:

javascriptjavascript
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Prints: 0, 1, 2

Best Practices

  • Declare variables at the top of their scope. This makes hoisting behavior obvious.
  • Use let and const instead of var. The TDZ catches accidental use-before-declaration.
  • Declare functions before calling them when possible. Even though hoisting allows the reverse, top-down ordering is easier to read.
  • Use function expressions or arrows for conditional or one-off functions.

For more on variable declarations, see JavaScript Function Scope Local vs Global Scope. For how hoisting interacts with function forms, see JavaScript Function Expressions vs Declarations.

Rune AI

Rune AI

Key Insights

  • Hoisting means declarations are processed before code execution, as if moved to the top of their scope.
  • Function declarations are fully hoisted -- you can call them before their definition line.
  • var variables are hoisted but initialized to undefined until the assignment line runs.
  • let and const are hoisted but inaccessible before their declaration (temporal dead zone).
  • Function expressions and arrow functions are not hoisted -- only their variable declaration is.
RunePowered by Rune AI

Frequently Asked Questions

Are let and const hoisted?

Yes, but differently from var. They are hoisted but placed in the temporal dead zone (TDZ). You cannot access them before the declaration line. Attempting to do so throws a ReferenceError.

Is hoisting a real process in the JavaScript engine?

Not exactly. The engine does not physically move code. Hoisting is a metaphor for how the engine processes declarations during the creation phase before executing code. It is easier to think of declarations being moved to the top.

Why does JavaScript have hoisting?

It was originally a side effect of how the engine parsed code in two phases. Function hoisting allows calling functions before their definition, which some developers find convenient. Modern best practice is to declare variables at the top of their scope to avoid confusion.

Conclusion

Hoisting is JavaScript's two-pass approach to running code: first it registers declarations, then it executes. Function declarations are fully hoisted and ready to call. var variables are hoisted but start as undefined. let and const are hoisted but locked in the temporal dead zone until their declaration line. Understanding these three behaviors prevents some of JavaScript's most confusing bugs.