The difference between let, const and var
let and const differ from var by being block scoped, by forbidding redeclaration and by using the Temporal Dead Zone instead of a silent undefined. On top of that const forbids reassignment, and neither let nor const adds a property to the global object in the browser, unlike var.
Theory
TL;DR
varis function or globally scoped;letandconstare block scoped.- All three are hoisted, but
varis initialised toundefined, whileletandconstsit in the TDZ and throw aReferenceError. - Redeclaration is allowed only for
var; forletandconstit is aSyntaxError. varandletcan be reassigned;constthrows aTypeError.constprotects the binding, not the contents: object properties and array elements can still change.- In the browser a top level
varbecomes a property ofwindow,letandconstdo not.
Quick example
function demo() {
if (true) {
var scopedToFunction = 'var';
let scopedToBlock = 'let';
const alsoScopedToBlock = 'const';
}
console.log(scopedToFunction); // 'var', visible in the whole function
console.log(scopedToBlock); // ReferenceError
console.log(alsoScopedToBlock); // ReferenceError
}
demo();The main differences
| Criterion | var | let | const |
|---|---|---|---|
| Scope | Function or global | Block ({ ... }) | Block ({ ... }) |
| Hoisting | Hoisted and initialised to undefined | Hoisted but not initialised (error when touched before the declaration) | Hoisted but not initialised (error when touched before the declaration) |
| Redeclaration | Allowed in the same scope | Error | Error |
| Reassignment | Allowed | Allowed | Not allowed |
| Added to the window object (in the browser) | Yes (window.varName) | No | No |
Scope
var is function scoped. An if block, a for block or a bare { ... } is no barrier for it: the variable belongs to the nearest function or to the global scope.
if (true) {
var a = 10;
}
console.log(a); // 10 (available outside the block)let and const are block scoped. They exist only inside the curly braces they were declared in.
if (true) {
let b = 20;
const c = 30;
}
console.log(b); // ReferenceError
console.log(c); // ReferenceError
letandconstonly "live" inside the{}block.
This is exactly why a for loop with let gives every iteration its own variable, while var gives one shared variable for the whole loop:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 3, 3, 3
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j)); // 0, 1, 2
}Hoisting and the Temporal Dead Zone
Every variable is "hoisted" to the top of its scope, but in different ways. var is immediately initialised to undefined, while let and const land in the Temporal Dead Zone (TDZ), a temporary dead zone where the variable already exists but cannot be touched yet.
console.log(a); // undefined (var is hoisted and initialised)
var a = 5;
console.log(b); // ReferenceError (b is in the TDZ)
let b = 10;
console.log(c); // ReferenceError (c is in the TDZ)
const c = 15;The TDZ is not a flaw but a safeguard: instead of a silent undefined that shows up later as a strange bug, you get an error on the exact line where the mistake was made.
Redeclaration, reassignment and const with objects
Only var lets you redeclare a variable in the same scope. Reassignment works for var and let, but not for const.
var x = 1;
var x = 2; // allowed
console.log(x); // 2
let y = 1;
let y = 2; // SyntaxError: Identifier 'y' has already been declared
const z = 1;
z = 2; // TypeError: Assignment to constant variableAn important nuance: const protects the binding, not the contents. The object or array the constant points at stays mutable.
const user = { name: 'Tim' };
user.name = 'Alex'; // the property can be changed
console.log(user.name); // 'Alex'
user = {}; // TypeError: the object itself cannot be reassignedWhen you need the contents to be genuinely immutable, use Object.freeze(user).
The global object and the rule of thumb
In the browser a top level var creates a property on the global object, let and const do not.
var x = 10;
let y = 20;
const z = 30;
console.log(window.x); // 10
console.log(window.y); // undefined
console.log(window.z); // undefinedvar adds the variable to the global object (window, globalThis), which makes it easy to overwrite someone else's global by accident. let and const do not, which makes the code safer.
| Situation | What to use | Why |
|---|---|---|
| The variable must change | let | Safer than var, block scoped |
| The variable must not change | const | States immutability explicitly |
| Legacy code (before ES6) | var | Obsolete, only for supporting old projects |
A simple rule to remember:
constby default.letif the value has to change.varnever in modern code.
Common mistakes
- Thinking
constmakes an object immutable. Only the binding of the name to the value is fixed; object fields and array elements change quite happily. - Believing
letandconstare not hoisted. They are hoisted, they just stay in the TDZ until the declaration line, hence aReferenceErrorrather thanundefined. - Using
varin a loop with asynchronous callbacks. One shared variable for the whole loop produces the classic3, 3, 3instead of0, 1, 2. - Relying on
var"just working" outside a block. That is a source of accidental overwrites and name collisions in a large file. - Expecting
window.myLetin the browser.letandconstvariables never reach the global object, so such access yieldsundefined.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.