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

Sorting Algorithms Explained
Bubble, selection, insertion, merge, quick, and heap sort: how each one works, where it shines, where it struggles, and animated demos that run the real algorithm code on the same starting data every time.
13 July 2026

Set Membership Testing: From Arrays to Bloom Filters
Every time Chrome warns you a site might be malicious, it just answered a membership question: is this URL in a set of bad ones? Here's how that check works, from a plain array to the probabilistic Bloom filter that makes it possible at scale.
10 July 2026

Why You Shouldn't Use Floating Point Math for Money in JavaScript
JavaScript numbers look like ordinary decimals, but they are stored as binary floating point. That is why 0.1 + 0.2 is not 0.3, and why checkout totals, tax lines, and ledger comparisons can quietly go wrong.
4 July 2026

RFC 10008: HTTP Finally Gets a Query Method
For thirty years, complex searches have been squeezed into GET query strings or smuggled through POST. RFC 10008 standardises QUERY: a safe, idempotent HTTP method with a request body. Here is why that matters and when you can actually use it.
3 July 2026
