Quick Comparison
| Property | var | let | const |
|---|---|---|---|
| Scope | Function (or global) | Block {} | Block {} |
| Hoisting | Yes — initialised as undefined | Yes — TDZ (not initialised) | Yes — TDZ (not initialised) |
| Reassignment | Yes | Yes | No |
| Redeclaration | Yes (same scope) | No — SyntaxError | No — SyntaxError |
| Must initialise | No | No | Yes |
| Use today? | Avoid | When reassignment needed | Default choice |
Scope: The Most Important Difference
var is function-scoped. It ignores block boundaries like if, for, and while. This leads to subtle bugs:
Hoisting Behaviour
All three keyword types are hoisted to the top of their scope, but in different ways:
The Temporal Dead Zone (TDZ)
The TDZ is the window between entering a scope and the point at which a let or const declaration is reached. The variable technically exists (it was hoisted) but is not yet initialised. Any access during this period throws a ReferenceError.
TDZ Is a Feature, Not a Bug
The TDZ catches a class of bugs where variables are used before they are properly set up. With var, accessing before declaration gives undefined — a silent, hard-to-debug problem. With let/const, you get an immediate, clear ReferenceError pointing exactly to the issue.
const — What It Does and Does Not Mean
Many developers think const means the value is immutable. This is incorrect. const prevents reassignment of the variable binding — not mutation of the value.
Object.freeze() is Shallow
Object.freeze() only freezes the top-level properties. Nested objects are NOT frozen: const obj = Object.freeze({ a: { b: 1 } }); obj.a.b = 99; — this works. For deep immutability, use a library like Immer or recursively freeze all nested objects.
Redeclaration Rules
Modern JavaScript Convention
The community-standard approach in modern JavaScript (and TypeScript):
- Use
constby default — for everything. It signals "this reference does not change" and prevents accidental reassignment bugs - Use
letonly when you need to reassign — loop counters (for let i), conditional assignments, accumulator variables - Never use
var— in modern projects with Babel/TypeScript transpilation, there is no reason to use var
How We Research and Update This Guide
We test the underlying formula or workflow, compare outputs with reliable references, and revise examples whenever the page content changes.
- The workflow or formula is tested directly in the tool and compared against independent reference examples.
- Examples are kept practical so readers can verify the result without hidden assumptions.
- Pages are revised whenever the interface, calculation flow, or surrounding guidance materially changes.
Frequently Asked Questions — var vs let vs const
var is function-scoped (or global if outside a function) and is hoisted to the top of its scope with value undefined. let is block-scoped (respects {} boundaries) and is hoisted but not initialised (temporal dead zone). const is block-scoped like let but cannot be reassigned after declaration. In modern JavaScript (ES6+), prefer const by default, use let when you need to reassign, and avoid var.
Hoisting is JavaScript's behaviour of moving variable and function declarations to the top of their scope during compilation. With var, the declaration (but not the value) is hoisted — accessing a var before its declaration gives undefined, not a ReferenceError. With let and const, the declaration is also hoisted but not initialised — accessing them before declaration throws a ReferenceError (this is the Temporal Dead Zone). Function declarations are fully hoisted (both declaration and body).
The TDZ is the period between entering a scope and the point where a let or const variable is declared. During this period, accessing the variable throws a ReferenceError even though it technically exists (it has been hoisted but not initialised). This prevents the confusing "undefined" behaviour of var and helps catch bugs where variables are used before they are declared.
No — const prevents reassignment of the variable binding, not mutation of the value. A const object's properties can still be changed: const user = {name: 'Alice'}; user.name = 'Bob'; // works. const arr = [1,2,3]; arr.push(4); // works. Only user = {} throws an error (reassignment). For true immutability, use Object.freeze() or immutable data libraries.
Use const for everything by default — it communicates "this value does not change" and prevents accidental reassignment. Use let when you explicitly need to reassign (loop counters, conditional assignments). Avoid var entirely in modern code — its function scope and hoisting behaviour lead to subtle bugs. The only reason to use var today is for supporting very old browsers without a transpiler (extremely rare in 2026).
No — this is another advantage over var. Declaring a variable with the same name twice using var silently overwrites it (a source of bugs). let and const throw a SyntaxError if you try to redeclare in the same scope: let x = 1; let x = 2; // SyntaxError. This prevents accidental name collisions in large files or when merging code.