HomeGuidesJavaScriptJavaScript var vs let vs const — Scope, Hoisting & TDZ Explained
JavaScript

JavaScript Variables and Scope: var, let, const Explained

var, let, and const behave very differently. Scope and hoisting questions appear in every JavaScript interview.

Examifyr·2026·6 min read

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);   // ReferenceError
Note: Use const by default. Use let when you need to reassign. Avoid var in modern JavaScript.

Hoisting

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"); };
Note: Hoisting is the source of many JavaScript bugs. Always declare variables at the top of their scope.

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;
Note: The TDZ exists from the start of the block until the declaration line. This is why let/const are "safer" than var — they fail loudly.

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());  // 3
Note: Closures are one of the most-tested JavaScript concepts. The inner function "closes over" the count variable.

The 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, 2
Note: This is the single most common JavaScript closure/scope interview question.

Exam 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

Next →
JavaScript Functions — Arrow Functions, this Keyword & bind/call/apply
← All JavaScript guides