Variable and function hoisting in JavaScript
Published
6 Dec 2022
If you have ever called a function before it appears in a file, or logged a var before its declaration line, you have already seen hoisting in action. The code did not move anywhere. JavaScript simply registered the declaration earlier than you expected.
Press Run on the examples below to execute each snippet and see the terminal output for yourself.
What hoisting actually is
JavaScript runs in two broad phases.
During the creation phase, the engine walks the current scope and registers every declaration. var variables are created and set to undefined. Function declarations are stored with their full definition. let and const are registered too, but they sit in the temporal dead zone until their declaration line runs, which means you cannot read them before that point.
During the execution phase, the engine runs your code top to bottom. Assignments, function calls, and console.log all happen here.
Hoisting is not the engine physically moving lines of code. It is the gap between when a declaration enters memory and when the rest of the line runs. That gap is what makes this work:
a = "Set the value";console.log(a);var a;The assignment and console.log happen during execution, but the var a declaration was already registered at the top of the scope with a value of undefined before the first line ran. By the time console.log(a) executes, the assignment has already given a its value.
Compare that with the more familiar order:
var a;a = "Set the value";console.log(a);Same output, same underlying behaviour.
Variable hoisting
The keyword you choose changes what gets hoisted and when you are allowed to read the binding.
var is hoisted and initialised to undefined
The first example above shows the classic pattern: the declaration is hoisted, the assignment stays where you wrote it, and the log runs after the value has been set.
let and const are hoisted but not initialised
let and const declarations are registered during the creation phase, but the engine throws if you access them before the declaration line executes.
a = "Not hoisted";let a;console.log(a);That is the temporal dead zone: the binding exists, yet reading or assigning to it before the declaration is a ReferenceError, not undefined.
The same rule applies to const:
console.log(message);const message = "Ready";There is no "helpful" undefined to fall back on. The variable simply is not readable yet.
Hoisting scope
Hoisting respects scope. A var declared inside a function is hoisted to the top of that function, not the whole file.
function functionScope() { a = "Scope hoisting"; console.log(a); var a;}
functionScope();console.log(a);Inside functionScope, the hoisted var a makes the assignment and log work. Outside the function, a was never declared in the outer scope, so the final console.log(a) throws a ReferenceError.
If you need a value to survive outside a function, declare it in that outer scope first.
Function hoisting
Function declarations are hoisted with their full body, which is why this works:
console.log(testFunction());
function testFunction() { return "Function output";}The engine registered testFunction during the creation phase, complete with its return value, before console.log ran.
Function expressions behave like other values. Only the declaration itself is hoisted, not the function value, and with let or const the binding is unusable until its line runs:
console.log(functionExpression());
let functionExpression = function () { return "Function output";};If you need to call something before its definition line, use a function declaration. If you are assigning a function to a variable, define it before you call it.
Practical advice
Hoisting is valid JavaScript, but it is easy to misread, especially when var, nested functions, and conditionals mix together.
A few habits keep the behaviour predictable:
- Prefer
letandconstovervarso accidental early access fails loudly. - Declare functions before you call them unless you deliberately rely on declaration hoisting.
- Treat function expressions and arrow functions like any other value: define first, use second.
- Remember that hoisting is scope-bound. Inner declarations do not leak outward.
Hoisting is not a quirk to memorise for trivia night. It is the reason JavaScript registers names before it runs your logic. Once you read code with those two phases in mind, the odd output stops looking random and starts looking inevitable.
Similar articles

AI-First Programming Languages: What They Are, and Whether You Should Use One Yet
In the last year a genuinely new category of programming language appeared: ones where the AI, not you, is the intended author. Here's what the categories are, which projects actually have traction, and which of them I'd be willing to put in production today.
9 Aug 2026

lucide-animated: Icon Motion That Knows When to Stop
lucide-animated wraps the entire lucide-react set in small, Motion-powered animations, same names, same currentColor stroke, one extra letter in the import. I wired it into a few real spots on this site and worked out where it earns its place and where it's just noise.
2 Aug 2026

Animated Gradient Borders: A Better Loading State for AI Interfaces
Spinners and skeleton screens assume a request either hasn't returned yet or has. AI responses don't work that way, they stream in over seconds while you're already reading them. A rotating gradient border solves the "still working" signal without covering up content the user is actively looking at.
31 July 2026

Pathfinding Algorithms Explained
A live stealth pathfinding demo, and the three buckets every algorithm in the dropdown falls into: Naive, Uninformed, and Informed. Deep dives live under /algorithms.
26 July 2026
