let vs. var vs. const
Three ways to declare JavaScript variables—one good default, one useful alternative, and one mostly for legacy code.
Definitions
let
Declares a block-scoped variable whose value can be reassigned.
var
Declares a function-scoped or global variable that can be reassigned and redeclared.
const
Declares a block-scoped binding that must be initialized and cannot be reassigned.
At a glance
| Behavior | const |
let |
var |
|---|---|---|---|
| Scope | Block | Block | Function or global |
| Reassignment | No | Yes | Yes |
| Redeclaration in the same scope | No | No | Yes |
| Initialization required | Yes | No | No |
| Before its declaration | Temporal dead zone | Temporal dead zone | Reads as undefined |
Block scope matters
let and const stay inside the nearest block. var ignores ordinary blocks and remains visible throughout the containing function.
if (true) {
const message = "inside";
let count = 1;
var legacy = "still visible";
}
console.log(legacy); // "still visible"
console.log(count); // ReferenceError
Hoisting and the temporal dead zone
All three declarations are processed before execution reaches them. The difference is initialization: var starts as undefined, while let and const cannot be accessed until their declaration runs.
console.log(oldValue); // undefined
var oldValue = 10;
console.log(newValue); // ReferenceError
let newValue = 10;
Why does the temporal dead zone occur?
When a scope is created, JavaScript creates its let and const bindings but leaves them uninitialized. The temporal dead zone is the time between entering that scope and executing the declaration. Reading the binding during that interval throws a ReferenceError.
This behavior makes block-scoped shadowing consistent across the entire block and exposes use-before-initialization bugs. It also ensures that a const binding receives its required initial value exactly at its declaration. By contrast, var is initialized to undefined as soon as its function scope is created.
const does not make values immutable
const protects the binding, not the object stored in it. Object properties and array contents may still change.
const user = { name: "Ada" };
user.name = "Grace"; // allowed
user = { name: "Linus" }; // TypeError
Choosing one
const apiUrl = "/api/users"; // binding stays the same
let page = 1;
page += 1; // binding changes
// Use var only when maintaining code that already depends on it.
Code output questions
Predict the result before opening each answer. Some snippets test behavior that is easy to miss.
-
What is printed?
console.log(total); var total = 5;Show answer
undefined. Thevardeclaration is hoisted and initialized before the assignment runs. -
Does this print
"outer"?let label = "outer"; { console.log(label); let label = "inner"; }Show answer
No. It throws a
ReferenceError. The innerlabelshadows the outer one throughout the block, but remains in its temporal dead zone until its declaration runs. -
What is printed after the loop finishes?
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); }Show answer
3, three times. Every callback closes over the same function-scopedi, and the callbacks run after the loop has changed it to3. Replacingvarwithletwould create a new binding for each iteration and print0,1,2. -
What is printed?
const profile = { name: "Ada" }; profile.name = "Grace"; console.log(profile.name);Show answer
"Grace". The object can be mutated; only reassignment of theprofilebinding is forbidden. -
What are the two outputs?
function inspect() { if (true) { var message = "ready"; let count = 1; } console.log(message); console.log(typeof count); } inspect();Show answer
"ready", then"undefined".varescapes theifblock because it is function-scoped.countis outside its scope, andtypeofon an undeclared name returns"undefined". -
Why is the global value not printed?
var mode = "global"; function show() { console.log(mode); var mode = "local"; } show();Show answer
It prints
undefined. The localvar modeis hoisted to the top ofshow, shadows the global binding, and is initialized toundefinedbefore the log runs. -
Does either line run?
const answer; console.log("finished");Show answer
No. Parsing fails with a
SyntaxErrorbecauseconstrequires an initializer. The program never begins executing.