var vs let vs const: JS Variable Declarations
Learn the key differences between var, let, and const in JavaScript: scope, reassignment, hoisting, and when to use each one in modern code.
JavaScript gives you three ways to declare a variable: var, let, and const. They look similar but behave differently in three important ways: scope, reassignment rules, and hoisting. Understanding these differences helps you avoid subtle bugs and write code that other developers can reason about quickly.
The following table summarizes the key differences at a glance. If you only remember one thing from this article, let it be the first row: var is function-scoped, let and const are block-scoped. That difference alone causes more bugs than the other two combined.
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function | Block | Block |
| Can reassign | Yes | Yes | No |
| Can redeclare | Yes | No | No |
| Hoisting | Yes, initialized to undefined | Yes, but not initialized | Yes, but not initialized |
| Introduced in | ES1 (1997) | ES6 (2015) | ES6 (2015) |
| Use in modern code | Avoid | When value changes | Default choice |
var: Function-Scoped and Error-Prone
var was the only way to declare variables in JavaScript before ES6 arrived in 2015. It is function-scoped, which means a var variable is visible everywhere inside the function that contains it, even outside the block where it was actually declared.
function demoVar() {
if (true) {
var message = "inside the if block";
}
console.log(message); // still accessible here
}
demoVar();The console prints "inside the if block". Even though message was declared inside the if block's curly braces, var makes it visible to the entire demoVar function. This behavior is called function scoping, and it surprises developers who expect block-level containment.
With var, you can also redeclare the same variable name without any error or warning. JavaScript silently overwrites the first declaration:
var count = 1;
var count = 2; // no error
console.log(count);The console prints 2. The first declaration is silently overwritten. In a large file, two declarations of the same var variable can end up far apart, and the second one looks like a fresh variable when it is actually destroying existing data.
let: Block-Scoped and Safe
let was introduced in ES6 to fix the scoping problems that var created. It is block-scoped, which means a let variable only exists inside the nearest set of curly braces.
function demoLet() {
if (true) {
let message = "inside the if block";
console.log(message); // works here
}
console.log(message); // ReferenceError
}
demoLet();Inside the if block, message prints normally. Outside the block, JavaScript throws a ReferenceError because message does not exist there. This is the behavior most developers intuitively expect.
let also prevents redeclaration. If you try to declare the same name twice in the same scope, JavaScript throws a SyntaxError before the code even runs:
let count = 1;
let count = 2; // SyntaxErrorIf you need to update the value of an existing let variable, just reassign it without the keyword: count = 2. Use the let keyword only when you are creating the variable for the first time.
const: Block-Scoped and Immutable Binding
const has the same block scoping as let, with one added rule: you cannot reassign the variable after the initial value is set. The name stands for constant, but it is the binding that is constant, not necessarily the value.
const apiKey = "abc123";
apiKey = "xyz789"; // TypeError: Assignment to constant variableconst also requires an initial value at declaration time. You cannot declare a const variable and assign to it later:
const apiKey; // SyntaxError: Missing initializerThis requirement makes sense. A constant with no value would serve no purpose, so JavaScript enforces that const variables always start with a meaningful value.
For objects and arrays, const prevents reassigning the variable but does not prevent changing the contents. The variable always points to the same object in memory, but you can modify what is inside that object:
const scores = [10, 20, 30];
scores.push(40); // allowed: mutating the array
console.log(scores);
scores = [50, 60]; // TypeError: reassignment not allowedThe console prints [10, 20, 30, 40]. The push method modified the existing array, which const allows. The attempted reassignment to a new array throws a TypeError.
Scope: The Biggest Difference
Scope determines where a variable is visible. The difference between function scope and block scope is the single most important distinction between var and the newer let and const.
A var variable declared inside any block within a function escapes that block and becomes visible to the entire function. A let or const variable stays contained inside its block. This containment is what you want because it prevents unrelated parts of your code from accidentally sharing variables.
The classic demonstration of this problem is the var loop bug with setTimeout:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}This prints 3, 3, 3 instead of 0, 1, 2. Because var i is function-scoped, all three callbacks share the same variable i. By the time the callbacks run, the loop has finished and i equals 3.
With let, each iteration gets its own copy of the loop variable, so each callback captures the correct value:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}This prints 0, 1, 2 as expected. The let keyword creates a fresh binding for i on each iteration, solving what was one of the most confusing JavaScript bugs for years.
Hoisting: Another Key Difference
Hoisting means JavaScript moves variable declarations to the top of their scope during compilation. All three keywords are hoisted, but they behave very differently when you try to access a variable before its declaration line.
console.log(a); // undefined (no error)
var a = 1;
console.log(b); // ReferenceError
let b = 2;var variables are hoisted and initialized to undefined, so accessing them early returns undefined instead of throwing an error. This fails silently and makes bugs harder to track down.
let and const variables are also hoisted, but they are not initialized. The time between the start of the scope and the declaration line is called the temporal dead zone. Accessing the variable during this zone throws a ReferenceError, which surfaces the bug immediately.
| Access before declaration | var | let | const |
|---|---|---|---|
| Behavior | Returns undefined | Throws ReferenceError | Throws ReferenceError |
| Bug detection | Silent, hard to find | Immediate error | Immediate error |
Which Should You Use?
For all new JavaScript code, follow this simple rule:
Start every variable with const. Switch to let only when you discover the value needs to be reassigned later, such as in loop counters, running totals, or state that updates in response to user actions.
Avoid var entirely. There is no situation in modern JavaScript where var is the best available choice.
// Good: const for fixed values
const taxRate = 0.08;
const freeShippingThreshold = 50;
// Good: let for values that change
let cartTotal = 0;
let itemCount = 0;
// Avoid: var
var legacyTotal = 100;For a deeper dive into var's problems, see why you should stop using var. To learn when to pick let over const in specific situations, read the let vs const decision guide.
Rune AI
Key Insights
- var is function-scoped; let and const are block-scoped.
- var allows redeclaration; let and const do not.
- var hoists with undefined; let and const hoist but throw errors if accessed early.
- const prevents reassignment but does not make objects immutable.
- Use const by default, let for mutable values, and avoid var.
Frequently Asked Questions
Should I ever use var in new JavaScript code?
Can I mix var, let, and const in the same file?
Is const faster than let?
Conclusion
var is function-scoped, allows redeclaration, and hoists with undefined. let is block-scoped, prevents redeclaration, and hoists without initialization. const is like let but also prevents reassignment. In modern JavaScript, use const by default, let when the value changes, and avoid var entirely.
More in this topic
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.
How to Loop Through Arrays Using JS for Loops Guide
Looping through arrays is the most practical use of JavaScript for loops. Learn how to access each element by index, avoid off-by-one errors, and choose between for, for...of, and forEach.