Posts

JavaScript Hoisting Explained

Hoisting is JavaScript’s setup behavior for declarations. It affects whether a function or variable can be used before the line where it appears in the file. That matters because function, var, let, and const do …

April 26, 2026 4 min read 707 words

In this article

Hoisting is JavaScript’s setup behavior for declarations.

It affects whether a function or variable can be used before the line where it appears in the file. That matters because function, var, let, and const do not all behave the same way.

This post is about two questions:

  1. When does JavaScript create the name?
  2. What value, if any, is available before the declaration line runs?

Hoisting is usually explained as “JavaScript moves declarations to the top.”

That explanation is common.

It is also a little too sloppy.

JavaScript is not physically rearranging your file before it runs. A better mental model is this:

Hoisting means JavaScript sets up declarations before the code starts executing.

What happens after that depends on the kind of declaration.

Here is the clean version of the model before we get into the individual keywords.

A diagram showing JavaScript’s setup phase preparing declarations before the execution phase runs statements in order.

Function Declarations Are Available Early

Function declarations can be called before they appear in the file.

sayHello();

function sayHello() {
  console.log("Hello");
}

// Hello

This works because JavaScript creates the function declaration during setup.

You can think of the function name as already pointing at the function value by the time execution begins.

setup phase:
+-- sayHello -> function value

execution phase:
+-- call sayHello()
+-- run function body

That does not mean you should always write code this way. It just means the language supports it.

var Is Available Early With undefined

var declarations are also set up before execution, but they start with the value undefined.

console.log(name);

var name = "Rob";

// undefined

JavaScript knows the name label exists. The assignment has not happened yet, so the label starts attached to undefined.

This mental model is close enough for everyday use:

var name;

console.log(name);

name = "Rob";

The label is available early. The intended value is not.

That is the part people trip over.

let and const Are Different

let and const are also known during setup, but you cannot use them before execution reaches the declaration.

That gap is called the temporal dead zone.

console.log(name);

const name = "Rob";

// ReferenceError: Cannot access 'name' before initialization

This error is helpful.

It tells you the label exists in this scope, but JavaScript will not let you use it yet.

That sounds fussy until it saves you from accidentally treating an uninitialized value as real data.

The temporal dead zone is a guardrail: the name exists, but JavaScript will not let you use it before it is ready.

A diagram showing the temporal dead zone before a let or const declaration is initialized, while staying inside the same block scope.

block scope
+-- name label exists during setup
+-- name cannot be used yet
+-- declaration line runs
+-- name gets its value

That is much easier to debug than quietly receiving undefined and accidentally building logic around a value that was not ready.

Hoisting Stays Inside Scope Boundaries

This is the important correction.

Hoisting does not move a declaration into a bigger scope.

If something is declared inside a function, it belongs to that function. If something is declared inside a block with let or const, it belongs to that block.

No promotion. No escape hatch.

Hoisting changes when a declaration is prepared. It does not change where that declaration belongs.

function exampleScope() {
  var functionMessage = "inside the function";
}

console.log(functionMessage);
// ReferenceError: functionMessage is not defined

functionMessage is hoisted inside exampleScope(), not outside of it.

Same idea with a block:

if (true) {
  const blockMessage = "inside the block";
}

console.log(blockMessage);
// ReferenceError: blockMessage is not defined

blockMessage belongs to the if block. Hoisting does not move it into the outer scope.

A Practical Summary

Here is the version I keep in my head:

  • function declarations are available before the line where they are written
  • var is available early, but its value starts as undefined
  • let and const are known early, but cannot be used before initialization
  • hoisting never moves a declaration outside its scope

That last point matters most.

Hoisting changes when a declaration is prepared.

It does not change where that declaration belongs.

These topics build on each other:

-Rob