JavaScript hoisting is the process during which variable and function declarations are moved to the top of their containing scope before code executes. Even though it may appear that declarations happen later in your code, JavaScript internally handles them first during the compilation phase. This invisible reordering shapes how your code behaves at runtime.
Hoisting happens in two steps: declaration hoisting and initialization. Variables declared with var
are hoisted and initialized to undefined
, allowing them to be referenced before their assignment. Function declarations are fully hoisted, making them callable anywhere in their scope. Understanding hoisting helps you predict outcomes and avoid confusion—even while writing playful code.
Hoisting of var
Declarations
When you declare a variable using var
, the declaration is hoisted, but the assignment stays where you wrote it. That means if you access the variable before its line, you get undefined
, not an error.
Here’s an example involving a magic potion:
<!DOCTYPE html>
<html>
<head>
<title>var Hoisting</title>
</head>
<body>
<script>
console.log('Potion:', potion); // prints "Potion: undefined"
var potion = 'Invisibility Elixir';
console.log('Potion after assignment:', potion);
</script>
</body>
</html>
Even though the console.log
appears before the var
assignment, JavaScript treats it as if var potion;
was at the top—leaving it undefined until assignment. So you get undefined
, not an error, showing how var
behaves under hoisting.
Hoisting of Function Declarations
Function declarations are completely hoisted—including their body—so you can call the function before its declaration appears in the code.
Check out this example with a spellcaster:
<!DOCTYPE html>
<html>
<head>
<title>Function Declaration Hoisting</title>
</head>
<body>
<script>
castSpell();
function castSpell() {
console.log('✨ Spell cast successfully!');
}
</script>
</body>
</html>
Here we call castSpell()
before it’s defined in the source. Because function declarations are hoisted fully, it works perfectly. That’s one difference when hoisting functions compared to variables.
Hoisting of Function Expressions
Function expressions (especially when assigned to var
) are treated like variables. Declarations are hoisted, but the function isn’t defined until that line—so calling before assignment leads to undefined
or an error.
Here’s how it behaves:
<!DOCTYPE html>
<html>
<head>
<title>Function Expression Hoisting</title>
</head>
<body>
<script>
try {
castSpell(); // Error: castSpell is not a function
} catch (e) {
console.log('Spell failed to cast:', e.message);
}
var castSpell = function() {
console.log('Spell cast after creation!');
};
castSpell(); // Works now
</script>
</body>
</html>
Even though castSpell
is declared with var
, JavaScript hoists only the variable, not the function. That causes an error on the first call, until the assignment is made.
Hoisting with let
and const
Unlike var
, let
and const
declarations are hoisted but not initialized. That means they exist in the scope early, but you can’t access them before the actual declaration line. Doing so throws a ReferenceError
because they’re in the Temporal Dead Zone (TDZ).
Consider this example:
<!DOCTYPE html>
<html>
<head>
<title>let/const Hoisting</title>
</head>
<body>
<script>
try {
console.log('Wizard name:', wizard);
} catch (e) {
console.log('Error:', e.message);
}
let wizard = 'Merlin';
console.log('Wizard after declaration:', wizard);
try {
console.log('Potion:', potion);
} catch (e) {
console.log('Error:', e.message);
}
const potion = 'Healing Brew';
console.log('Potion after declaration:', potion);
</script>
</body>
</html>
Trying to access wizard
or potion
before declaration triggers a reference error—because they’re hoisted but not accessible until the code reaches their definitions.
Block Scope and Hoisting Inside Blocks
Hoisting behaves differently inside blocks like if
or for
. Variables declared with var
are hoisted to the containing function or global scope, while let
and const
stay within block scope.
Imagine a guard tower where declarations are scoped inside the tower:
<!DOCTYPE html>
<html>
<head>
<title>Block Scope Hoisting</title>
</head>
<body>
<script>
if (true) {
var treasure = 'Gold';
let guard = 'Elf';
const secret = 'Hidden Map';
}
console.log('Treasure:', treasure); // Gold
try {
console.log('Guard:', guard);
} catch (e) {
console.log('Error:', e.message); // guard is not defined
}
try {
console.log('Secret:', secret);
} catch (e) {
console.log('Error:', e.message); // secret is not defined
}
</script>
</body>
</html>
Here treasure
is accessible outside the if
block because var
hoists to the outer function scope. But guard
and secret
are block-scoped via let
and const
, making them unavailable outside.
Hoisting of Class Declarations
Class declarations are hoisted in a manner similar to let
and const
: they exist in scope early but aren’t initialized until the declaration line, so accessing them too soon causes a ReferenceError
.
Here’s a wizard class example:
<!DOCTYPE html>
<html>
<head>
<title>Class Hoisting</title>
</head>
<body>
<script>
try {
const mage = new Wizard();
} catch (e) {
console.log('Error:', e.message);
}
class Wizard {
constructor() {
this.name = 'Gandalf';
console.log('Wizard created:', this.name);
}
}
const mage = new Wizard();
</script>
</body>
</html>
Attempting to instantiate the class before its declaration fails, because the class isn’t initialized until its line—and that reflects how hoisting treats classes.
Combined Hoisting Example (Playful Story)
Let’s combine everything into one fun, magical storyline:
<!DOCTYPE html>
<html>
<head>
<title>Hoisting Story</title>
</head>
<body>
<script>
// At this point, "potion" is hoisted (because it's declared with var),
// so it exists but holds "undefined"
console.log('Potion:', potion); // undefined
// Assigning a value after the first log
potion = 'Invisibility Potion';
// "creature" is declared with let, so it's in the "temporal dead zone"
// Accessing it before declaration causes a ReferenceError
try {
console.log('Creature:', creature); // ReferenceError
} catch(e) {
console.log('Error:', e.message);
}
// The function summon is hoisted completely (declaration and body),
// so we can call it even before its place in the code
// BUT it uses "creature", which isn't defined yet here, so it would throw if uncommented
// console.log('Summon:', summon()); // Would fail because "creature" isn't defined yet
var potion; // Hoisted to the top with "undefined"
let creature = 'Dragon'; // Not hoisted like var; lives in temporal dead zone
function summon() {
return 'Summoned a ' + creature;
}
// Now that "creature" is defined, summon works just fine
console.log('Summon:', summon()); // "Summoned a Dragon"
// Class expressions are not hoisted; they exist only after this line
const wizard = class Wizard {};
console.log('Wizard class exists now:', typeof wizard); // "function"
</script>
</body>
</html>
This code shows how hoisting works in JavaScript. Variables declared with var
(like potion
) are lifted to the top and get the value undefined
until you assign them. But variables declared with let
(like creature
) are also lifted but stay in a special “dead zone” and cause an error if used too early. Functions (like summon
) are fully hoisted, so you can use them before they’re written in the code — as long as the things they use are ready. Classes (like wizard
) are not hoisted at all. They only exist after you create them. This shows how different types of declarations behave before and after they are defined.
Conclusion
Understanding hoisting in JavaScript means knowing which declarations get moved above and how they behave at runtime. var
variables are hoisted and initialized to undefined
, functions are fully hoisted, and function expressions and var
‑assigned functions behave differently. let
, const
, and classes are hoisted but enter the TDZ until initialization. Seeing hoisting in action helps clarify why code runs the way it does—even when declaration order surprises you.
References
If you’re curious to explore further or want to double-check what you’ve learned, these trusted documentation pages offer more detailed explanations and examples:
- MDN Web Docs: Hoisting
Clear explanation of hoisting and its effects. - MDN Web Docs: var
Description ofvar
and its scoping/hoisting behavior. - MDN Web Docs: let
Details onlet
, TDZ, and block scope. - MDN Web Docs: const
Explanation ofconst
declaration and hoisting behavior.