What is the difference between var, let and const? Explain hoisting and the temporal dead zone.
var is function-scoped and is hoisted with the value undefined. let and const are block-scoped and are hoisted too, but cannot be used before their declaration line; that gap is the temporal dead zone. const also cannot be reassigned, though an object it holds can still be changed.
Explain it simply
Think of a classroom attendance register. Before the class starts, the teacher writes every student's name in the register. That is hoisting: JavaScript reads your code first and notes every variable name in the scope before running a single line.
What happens if someone calls a name early is different:
- With var, the name is in the register with a blank next to it. Asking for it early gives you
undefined, with no error. - With let and const, the name is in the register but marked "not arrived yet". Asking for it early is an error. The time between the start of the scope and the line where the variable is declared is called the temporal dead zone (TDZ).
The other big difference is scope. var belongs to the whole function. let and const belong to the nearest pair of curly braces { }, such as an if block or a loop.
Example
Hoisting, side by side:
console.log(price); // prints undefined (var is hoisted with undefined)
var price = 100;
console.log(total); // ReferenceError: Cannot access 'total' before initialization
let total = 200;Block scope, the TDZ and const, checked by running:
if (true) {
var fromVar = 1;
let fromLet = 2;
}
console.log(typeof fromVar, typeof fromLet); // Output: number undefined
try {
console.log(score);
let score = 10;
} catch (e) {
console.log(e.name); // Output: ReferenceError
}
const user = { name: "Asha" };
user.name = "Ravi"; // allowed: we change the object, not the variable
console.log(user.name); // Output: Ravi
try {
user = { name: "Kiran" }; // not allowed: reassigning a const
} catch (e) {
console.log(e.name); // Output: TypeError
}How it works
| var | let | const | |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisted | Yes, set to undefined | Yes, but in the TDZ | Yes, but in the TDZ |
| Re-declare in same scope | Allowed | Error | Error |
| Reassign | Allowed | Allowed | Error |
| Must initialise at declaration | No | No | Yes |
Function declarations are hoisted completely, body included, which is why you can call them above the line where they are written. Function expressions assigned to let or const follow the TDZ rules.
Real-world use
Modern style is: use const by default, let when the value must change (a loop counter, a running total), and avoid var. This makes code easier to read, because a reader knows a const binding never points to something else.
Common mistakes
- Believing
constmakes an object or array frozen. It only stops reassignment. UseObject.freezefor a shallow freeze. - Saying
letandconstare "not hoisted". They are hoisted; the TDZ is what stops early access. - Using
varin a loop with callbacks, so every callback sees the final value. - Thinking
typeofis always safe.typeof xon a variable in its TDZ still throws.
What to say in the interview
"var is function-scoped, can be re-declared, and is hoisted with the value undefined, so reading it early gives undefined. let and const are block-scoped. They are also hoisted, but they stay uninitialised until their declaration runs, and accessing them before that throws a ReferenceError; that period is the temporal dead zone. const cannot be reassigned, but if it holds an object, the object's properties can still change. I use const by default and let only when I need to reassign."
Follow-up questions
- Why does a
forloop withvarandsetTimeoutprint the same number three times? - Is
const arr = []; arr.push(1)valid? - Are function declarations and function expressions hoisted the same way?
Go deeper
Read these once the basics above feel easy.
At the top level of a classic script, var creates a property on the global object (window.price in a browser), while let and const do not. In a for (let i = 0; ...) loop, the engine creates a new binding of i for each iteration and copies the value forward, which is why closures in the loop each see their own i. See closures for that example.