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

Production Agentic AI: Permissions, Observability, and Kill Switches
Putting an agentic AI system into production is a different engineering problem to putting it into staging. Once it is live, the governance question shifts from "will it behave?" to "how do we know if it stops behaving, and what do we do about it?" A practical guide to production permissions, observability architecture, and kill switches for Australian engineering leaders in regulated industries.
25 Sept 2026

What Australian CTOs Should Put in the AI Governance Pack
The board is asking. The regulator is watching. The CTO is the one who has to translate "AI governance" from a policy concept into a document set that actually governs something. Here is what a credible AI governance pack looks like for an Australian organisation in 2026, and what each section needs to do to survive scrutiny.
21 Sept 2026

Governing Agentic AI: Human-in-the-Loop Patterns When Tools Can Act (MCP Edition)
Agentic AI doesn't just recommend. It acts. That changes governance from a policy question into an engineering question: which patterns actually limit blast radius, satisfy a human reviewer, and survive scrutiny from a regulator or court? A practical playbook for MCP-connected agents, with a checklist for regulated industries.
16 Sept 2026

Vercel Can Do WebSockets Now. Here's Where a Chat Room Still Breaks It.
Vercel shipped native WebSocket support in 2026, so a Function really can hold a socket open. But the connection is still a function invocation with a duration ceiling and no fan-out. Here's where that's fine for a chat app, where it isn't, and how Supabase Realtime covers the gap.
15 Aug 2026
