JavaScript Variables and Scope: var, let, const Explained
var, let, and const behave very differently. Scope and hoisting questions appear in every JavaScript interview.
var vs let vs const
var is function-scoped and hoisted. let and const are block-scoped and not initialised until their declaration is reached.
var x = 1; // function-scoped, hoisted
let y = 2; // block-scoped
const z = 3; // block-scoped, can't be reassigned
// Block scope example:
{
var a = 10; // visible outside block
let b = 20; // only visible inside block
}
console.log(a); // 10
console.log(b); // ReferenceErrorHoisting
Variable declarations with var are hoisted to the top of their function scope and initialised to undefined. Function declarations are fully hoisted.
console.log(x); // undefined (hoisted, not initialised)
var x = 5;
console.log(x); // 5
// Function declarations are fully hoisted:
greet(); // "Hello" — works before declaration
function greet() { console.log("Hello"); }
// Function expressions are NOT fully hoisted:
sayHi(); // TypeError: sayHi is not a function
var sayHi = function() { console.log("Hi"); };Temporal Dead Zone (TDZ)
let and const are hoisted but not initialised. Accessing them before their declaration throws a ReferenceError — this gap is the Temporal Dead Zone.
console.log(x); // ReferenceError: Cannot access 'x' before initialization let x = 5; // typeof doesn't help with let/const in TDZ: console.log(typeof x); // ReferenceError (unlike var which gives "undefined") let x = 5;
Closures
A closure is a function that retains access to its outer scope even after the outer function has returned.
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3The classic var-in-loop trap
Using var in a loop creates one shared variable — all closures reference the same value. let creates a new binding per iteration.
// BUG with var:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// prints: 3, 3, 3
// FIX with let:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// prints: 0, 1, 2Exam tip
The var-in-loop closure trap is the #1 JavaScript scope question. Know that var creates one binding for all loop iterations, while let creates a new binding per iteration.
Think you're ready? Prove it.
Take the free JavaScript readiness test. Get a score, topic breakdown, and your exact weak areas.
Take the free JavaScript test →Free · No sign-up · Instant results