Published by Hashan Madhushanka
- 02 min read
Understanding var, let, and const in JavaScript: A Complete Guide
JavaScript offers three ways to declare variables: var, let, and const. While they might seem interchangeable at first glance, understanding their differences is crucial for writing clean, bug-free code. Let’s dive into what makes each one unique and when you should use them.
The Evolution of Variable Declarations
Before ES6 (ECMAScript 2015), var was the only way to declare variables in JavaScript. However, its quirky behavior led to many bugs and confusion. ES6 introduced let and const to address these issues, giving developers more predictable and safer ways to work with variables.
Scope: Where Your Variables Live
var: Function Scope
Variables declared with var are function-scoped, meaning they’re accessible anywhere within the function where they’re declared:
function example() {
if (true) {
var x = 10;
}
console.log(x); // 10 - accessible outside the if block!
}
This behavior can lead to unexpected results. The variable x “leaks” out of the if block because var doesn’t respect block scope.
Both let and const are block-scoped, meaning they only exist within the nearest set of curly braces:
function example() {
if (true) {
let x = 10;
const y = 20;
}
console.log(x); // ReferenceError: x is not defined
console.log(y); // ReferenceError: y is not defined
}
This behavior is more intuitive and helps prevent bugs by keeping variables contained to their logical scope.
Hoisting: The Invisible Move
var: Hoisted and Initialized
Variables declared with var are hoisted to the top of their scope and initialized with undefined:
console.log(x); // undefined (no error!)
var x = 5;
console.log(x); // 5
This is equivalent to:
var x = undefined;
console.log(x);
x = 5;
console.log(x);
let and const: Temporal Dead Zone
While let and const are also hoisted, they’re not initialized. Accessing them before declaration results in a ReferenceError:
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 5;
The period between entering scope and the actual declaration is called the Temporal Dead Zone (TDZ). This helps catch errors early in development.
Reassignment: Can You Change It?
var and let: Reassignable
Both var and let allow you to reassign values:
var x = 10;
x = 20; // No problem
let y = 10;
y = 20; // Also fine
const: Immutable Binding
Variables declared with const cannot be reassigned:
const x = 10;
x = 20; // TypeError: Assignment to constant variable
However, it’s important to note that const creates an immutable binding, not an immutable value. For objects and arrays, you can still modify their contents:
const user = { name: 'Alice' };
user.name = 'Bob'; // This works!
user.age = 30; // This also works!
const numbers = [1, 2, 3];
numbers.push(4); // This works!
numbers[0] = 10; // This also works!
// But you can't reassign the entire variable
user = { name: 'Charlie' }; // TypeError!
numbers = [5, 6, 7]; // TypeError!
Redeclaration Rules
var: Allows Redeclaration
You can redeclare the same variable multiple times with var:
var x = 10;
var x = 20; // No error
console.log(x); // 20
This can lead to accidentally overwriting variables in large codebases.
let and const: No Redeclaration
Both let and const prevent redeclaration within the same scope:
let x = 10;
let x = 20; // SyntaxError: Identifier 'x' has already been declared
const y = 10;
const y = 20; // SyntaxError: Identifier 'y' has already been declared
Best Practices: When to Use Each
Use const by Default
Start with const for all declarations. This makes your code more predictable and helps prevent accidental reassignments:
const API_KEY = 'your-api-key';
const MAX_USERS = 100;
const config = { timeout: 5000 };
Use let When Reassignment Is Needed
Only use let when you know the variable will need to be reassigned:
let counter = 0;
counter++;
let result = '';
for (let i = 0; i < 10; i++) {
result += i;
}
Common Pitfalls and How to Avoid Them
Const Doesn't Mean Immutable
Remember that const only prevents reassignment, not mutation:
const settings = { theme: 'dark' };
settings.theme = 'light'; // This works!
// To truly make it immutable:
const settings = Object.freeze({ theme: 'dark' });
settings.theme = 'light'; // Silently fails (throws error in strict mode)
Conclusion
Understanding the differences between var, let, and const is fundamental to writing modern JavaScript. Here's a quick recap:
- const: Use by default. Block-scoped, cannot be reassigned, prevents redeclaration.
- let: Use when reassignment is necessary. Block-scoped, prevents redeclaration.
- var: Avoid in modern code. Function-scoped, allows redeclaration, hoisted behavior can cause bugs.
By following the pattern of "const by default, let when necessary, var never," you'll write cleaner, more maintainable code that's easier to debug and reason about. Your future self (and your teammates) will thank you for it.