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.

7 min read

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.

Featurevarletconst
ScopeFunctionBlockBlock
Can reassignYesYesNo
Can redeclareYesNoNo
HoistingYes, initialized to undefinedYes, but not initializedYes, but not initialized
Introduced inES1 (1997)ES6 (2015)ES6 (2015)
Use in modern codeAvoidWhen value changesDefault 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.

javascriptjavascript
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:

javascriptjavascript
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.

javascriptjavascript
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:

javascriptjavascript
let count = 1;
let count = 2; // SyntaxError

If 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.

javascriptjavascript
const apiKey = "abc123";
apiKey = "xyz789"; // TypeError: Assignment to constant variable

const also requires an initial value at declaration time. You cannot declare a const variable and assign to it later:

javascriptjavascript
const apiKey; // SyntaxError: Missing initializer

This 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:

javascriptjavascript
const scores = [10, 20, 30];
scores.push(40);       // allowed: mutating the array
console.log(scores);
 
scores = [50, 60];     // TypeError: reassignment not allowed

The 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.

Scope visibility of var vs 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:

javascriptjavascript
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:

javascriptjavascript
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.

javascriptjavascript
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 declarationvarletconst
BehaviorReturns undefinedThrows ReferenceErrorThrows ReferenceError
Bug detectionSilent, hard to findImmediate errorImmediate 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.

javascriptjavascript
// 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

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.
RunePowered by Rune AI

Frequently Asked Questions

Should I ever use var in new JavaScript code?

Almost never. let and const replaced var in ES6 (2015) and fix its scoping problems. The only situation where var might still be useful is when you need function-scoped behavior in a specific legacy context.

Can I mix var, let, and const in the same file?

Technically yes, but it makes your code harder to read and reason about. Stick to let and const consistently. If you are working in an older codebase that uses var, refactor gradually.

Is const faster than let?

No meaningful performance difference exists between const and let in modern JavaScript engines. Choose based on intent: const says this value should not change, let says it can.

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.