Javascript 5 🧬 variable hoisting and var vs let
Hoisting and scoping are two fundamental concepts that explain why var, let, and const behave differently. Understanding them helps you avoid confusing bugs.
A Quick Look at the Examples
// Example 1: var hoisting
console.log(x); // undefined (not an error!)
var x = 5;
// Example 2: Function hoisting
greet(); // "Hello!" (works!)
function greet() {
console.log("Hello!");
}
// Example 3: let hoisting
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 10;
// Example 4: var re-declaration
var d = 20;
var d = 30; // ✅ No error — re-declaration allowed
console.log(d); // 30
// Example 5: let re-declaration
let e = 40;
let e = 50; // ❌ SyntaxError: Identifier 'e' has already been declared
a. Variable Hoisting
Hoisting is a JavaScript behavior where variable and function declarations are moved to the top of their containing scope during the compilation phase.
This means you can use variables or functions before they are declared — but the behavior depends on how they were declared.
How Each Declaration Type Behaves
| Declaration | Hoisted? | Initialized? | Can Use Before Declaration? |
|---|---|---|---|
var | ✅ Yes | ✅ Yes (as undefined) | ✅ Yes — returns undefined |
let | ✅ Yes | ❌ No (Temporal Dead Zone) | ❌ No — ReferenceError |
const | ✅ Yes | ❌ No (Temporal Dead Zone) | ❌ No — ReferenceError |
function | ✅ Yes | ✅ Yes (entire function) | ✅ Yes — works fully |
1. var Hoisting
With var, hoisting happens — the variable is initialized with undefined.
console.log(x); // undefined (not an error!)
var x = 5;
console.log(x); // 5
// What JavaScript actually does:
var x; // Declaration hoisted to top
console.log(x); // undefined
x = 5; // Assignment stays in place
console.log(x); // 5
2. let and const Hoisting (Temporal Dead Zone)
With let and const, hoisting still happens, but the variables are in a Temporal Dead Zone (TDZ) until they are declared.
console.log(a); // ❌ ReferenceError: Cannot access 'a' before initialization
let a = 10;
What is the Temporal Dead Zone?
The TDZ is the period between entering a scope and the variable’s declaration. During this time, the variable exists but cannot be accessed.
┌─────────────────────────────────────┐
│ Scope starts here │
│ │
│ ┌─────────────────────────────┐ │
│ │ TEMPORAL DEAD ZONE (TDZ) │ │
│ │ Variable exists but │ │
│ │ cannot be accessed │ │
│ └─────────────────────────────┘ │
│ │
│ let a = 10; ← TDZ ends here │
│ │
│ console.log(a); ← ✅ Now accessible│
└─────────────────────────────────────┘
3. Function Declaration Hoisting
Function declarations are hoisted completely — the entire function is moved to the top.
greet(); // ✅ "Hello!" — works!
function greet() {
console.log("Hello!");
}
4. Function Expression Hoisting (Not Hoisted the Same Way)
Function expressions (assigned to variables) follow the variable’s hoisting rules, not the function’s.
// var function expression
sayHi(); // ❌ TypeError: sayHi is not a function
var sayHi = function() {
console.log("Hi!");
};
// sayHi is hoisted as undefined, so calling it fails
// let function expression
sayBye(); // ❌ ReferenceError: Cannot access 'sayBye' before initialization
let sayBye = function() {
console.log("Bye!");
};
Hoisting Summary
| Type | Hoisted | Initialized | Example |
|---|---|---|---|
var | ✅ Yes | undefined | console.log(x); var x = 5; → undefined |
let | ✅ Yes | TDZ (no access) | console.log(a); let a = 10; → ReferenceError |
const | ✅ Yes | TDZ (no access) | console.log(b); const b = 10; → ReferenceError |
function | ✅ Yes | Fully initialized | greet(); function greet() {} → works |
b. var vs let
The key differences between var and let come down to scope, hoisting, and re-declaration.
Comparison Table
| Feature | var | let |
|---|---|---|
| Scope | Function-scoped | Block-scoped |
| Hoisting | Hoisted, initialized as undefined | Hoisted, but in TDZ |
| Re-declaration | ✅ Allowed in same scope | ❌ Not allowed in same scope |
| Re-assignment | ✅ Allowed | ✅ Allowed |
| Global object property | ✅ Yes (window.x) | ❌ No |
| Introduced | ES1 (1997) | ES6 (2015) |
1. Scope
var is function-scoped:
function example() {
if (true) {
var x = 10;
}
console.log(x); // ✅ 10 — accessible outside the block!
}
example();
let is block-scoped:
function example() {
if (true) {
let x = 10;
console.log(x); // ✅ 10 — inside the block
}
console.log(x); // ❌ ReferenceError: x is not defined
}
example();
2. Hoisting
var — hoisted and initialized as undefined:
console.log(x); // undefined (no error)
var x = 5;
let — hoisted but in Temporal Dead Zone:
console.log(a); // ❌ ReferenceError: Cannot access 'a' before initialization
let a = 10;
3. Re-declaration
var — re-declaration is allowed:
var d = 20;
var d = 30; // ✅ No error
console.log(d); // 30
// This can cause bugs — the second declaration silently overwrites
let — re-declaration causes an error:
let e = 40;
let e = 50; // ❌ SyntaxError: Identifier 'e' has already been declared
4. Global Object Property
var at global scope creates a property on window:
var globalVar = "hello";
console.log(window.globalVar); // "hello"
let at global scope does NOT create a window property:
let globalLet = "hello";
console.log(window.globalLet); // undefined
5. Loops — A Classic Difference
var in loops (bug-prone):
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3 (all callbacks see the same i)
let in loops (correct behavior):
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2 (each iteration gets its own i)
Complete Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Variable Hoisting and var vs let</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #f8f9fa;
color: #333;
line-height: 1.6;
}
h1 { color: #007bff; border-bottom: 3px solid #007bff; padding-bottom: 10px; }
h2 { color: #28a745; border-left: 4px solid #28a745; padding-left: 15px; margin-top: 30px; }
.demo-box {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin: 15px 0;
}
pre {
background: #1e1e1e;
color: #d4d4d4;
padding: 15px;
border-radius: 8px;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.8;
}
.keyword { color: #569cd6; }
.string { color: #ce9178; }
.number { color: #b5cea8; }
.function { color: #dcdcaa; }
.comment { color: #6a9955; }
.boolean { color: #569cd6; }
.error { color: #f48771; }
.success { color: #4ec9b0; }
#output {
background: #e9ecef;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
min-height: 40px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
border-left: 4px solid #007bff;
white-space: pre-wrap;
}
</style>
</head>
<body>
<h1>Variable Hoisting and var vs let</h1>
<div class="demo-box">
<h2>1. var Hoisting</h2>
<pre>
console.log(x); <span class="comment">// undefined (not an error!)</span>
<span class="keyword">var</span> x = <span class="number">5</span>;
<span class="comment">// What JavaScript actually does:</span>
<span class="keyword">var</span> x; <span class="comment">// Declaration hoisted</span>
console.log(x); <span class="comment">// undefined</span>
x = <span class="number">5</span>; <span class="comment">// Assignment stays</span>
console.log(x); <span class="comment">// 5</span>
</pre>
</div>
<div class="demo-box">
<h2>2. let and const — Temporal Dead Zone</h2>
<pre>
console.log(a); <span class="error">// ❌ ReferenceError: Cannot access 'a' before initialization</span>
<span class="keyword">let</span> a = <span class="number">10</span>;
console.log(b); <span class="error">// ❌ ReferenceError: Cannot access 'b' before initialization</span>
<span class="keyword">const</span> b = <span class="number">20</span>;
</pre>
</div>
<div class="demo-box">
<h2>3. Function Hoisting</h2>
<pre>
greet(); <span class="success">// ✅ "Hello!" — works!</span>
<span class="keyword">function</span> <span class="function">greet</span>() {
console.log(<span class="string">"Hello!"</span>);
}
<span class="comment">// Function expressions follow variable hoisting rules</span>
sayHi(); <span class="error">// ❌ TypeError: sayHi is not a function</span>
<span class="keyword">var</span> sayHi = <span class="keyword">function</span>() {
console.log(<span class="string">"Hi!"</span>);
};
</pre>
</div>
<div class="demo-box">
<h2>4. Re-declaration</h2>
<pre>
<span class="comment">// var — re-declaration allowed</span>
<span class="keyword">var</span> d = <span class="number">20</span>;
<span class="keyword">var</span> d = <span class="number">30</span>; <span class="success">// ✅ No error</span>
console.log(d); <span class="comment">// 30</span>
<span class="comment">// let — re-declaration NOT allowed</span>
<span class="keyword">let</span> e = <span class="number">40</span>;
<span class="keyword">let</span> e = <span class="number">50</span>; <span class="error">// ❌ SyntaxError: Identifier 'e' has already been declared</span>
</pre>
</div>
<div class="demo-box">
<h2>5. Scope Difference</h2>
<pre>
<span class="comment">// var — function scoped</span>
<span class="keyword">function</span> <span class="function">varExample</span>() {
<span class="keyword">if</span> (<span class="boolean">true</span>) {
<span class="keyword">var</span> x = <span class="number">10</span>;
}
console.log(x); <span class="success">// ✅ 10 — accessible outside the block!</span>
}
varExample();
<span class="comment">// let — block scoped</span>
<span class="keyword">function</span> <span class="function">letExample</span>() {
<span class="keyword">if</span> (<span class="boolean">true</span>) {
<span class="keyword">let</span> y = <span class="number">10</span>;
}
console.log(y); <span class="error">// ❌ ReferenceError: y is not defined</span>
}
letExample();
</pre>
</div>
<div class="demo-box">
<h2>6. Live Output — Hoisting Demo</h2>
<p>This demo safely demonstrates hoisting (errors are caught):</p>
<div id="output">Loading...</div>
</div>
<script>
// ============================================
// Variable Hoisting — Live Demo
// ============================================
let results = [];
// 1. var hoisting — works, returns undefined
results.push('📌 var hoisting:');
results.push(' console.log(x) → ' + (typeof x === 'undefined' ? 'undefined ✅' : x));
var x = 5;
results.push(' After assignment: x = ' + x);
results.push('');
// 2. let hoisting — Temporal Dead Zone
results.push('📌 let hoisting (TDZ):');
try {
// This would throw, so we simulate the error message
results.push(' console.log(a) → ❌ ReferenceError: Cannot access \'a\' before initialization');
} catch (e) {
results.push(' ' + e.message);
}
let a = 10;
results.push(' After declaration: a = ' + a);
results.push('');
// 3. Function hoisting — works
results.push('📌 Function hoisting:');
results.push(' greet() → ' + greet());
function greet() {
return '"Hello!" ✅';
}
results.push('');
// 4. var re-declaration
results.push('📌 var re-declaration:');
var d = 20;
var d = 30; // No error
results.push(' var d = 20; var d = 30; → d = ' + d + ' ✅');
results.push('');
// 5. let re-declaration (would throw, so we describe it)
results.push('📌 let re-declaration:');
results.push(' let e = 40; let e = 50; → ❌ SyntaxError');
results.push(' (Cannot re-declare a let variable in the same scope)');
results.push('');
// 6. Scope difference
results.push('📌 Scope difference:');
results.push(' var → function-scoped (accessible outside blocks)');
results.push(' let → block-scoped (NOT accessible outside blocks)');
// Display results
document.getElementById('output').textContent = results.join('\n');
</script>
</body>
</html>
Quick Reference
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function / Global | Block | Block |
| Hoisting | ✅ Yes (as undefined) | ⚠️ Yes (TDZ) | ⚠️ Yes (TDZ) |
| Re-declaration | ✅ Allowed | ❌ Error | ❌ Error |
| Re-assignment | ✅ Allowed | ✅ Allowed | ❌ Error |
| Global object property | ✅ Yes | ❌ No | ❌ No |
| Introduced | ES1 (1997) | ES6 (2015) | ES6 (2015) |
Hoisting Summary
| Declaration | Can Use Before? | Value Before Declaration |
|---|---|---|
var | ✅ Yes | undefined |
let | ❌ No | ReferenceError (TDZ) |
const | ❌ No | ReferenceError (TDZ) |
function | ✅ Yes | Fully initialized |
The Temporal Dead Zone (TDZ)
┌─────────────────────────────────────┐
│ Scope starts here │
│ │
│ ┌─────────────────────────────┐ │
│ │ TEMPORAL DEAD ZONE (TDZ) │ │
│ │ Variable exists but │ │
│ │ cannot be accessed │ │
│ └─────────────────────────────┘ │
│ │
│ let a = 10; ← TDZ ends here │
│ │
│ console.log(a); ← ✅ Now accessible│
└─────────────────────────────────────┘
Best Practices
✅ Do This:
// Use const by default
const PI = 3.14159;
// Use let when reassignment is needed
let count = 0;
count++;
// Declare variables before using them
let name = "Alice";
console.log(name);
// Use block scope to your advantage
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2 ✅
}
❌ Don’t Do This:
// Don't use var
var name = "Alice"; // Use let or const
// Don't use variables before declaration
console.log(x); // undefined — confusing!
var x = 5;
// Don't re-declare variables
var d = 20;
var d = 30; // Silent bug!
// Don't use var in loops
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3 ❌
}
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
Using var in loops | Callbacks see the final value | Use let |
Accessing let before declaration | ReferenceError | Declare before use |
Re-declaring var | Silent overwrite bugs | Use let/const |
Assuming let isn’t hoisted | It is — but in TDZ | Declare at the top |
| Function expressions hoisting | Not fully hoisted | Define before calling |
Pro Tip: Avoid var entirely in modern JavaScript. Use const by default, and let only when reassignment is needed. This eliminates hoisting surprises, prevents accidental re-declarations, and makes your code more predictable. The Temporal Dead Zone (TDZ) is your friend — it catches bugs where you try to use a variable before declaring it. Remember: hoisting still happens with let and const — they’re just protected by the TDZ until declaration!
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!