One of the fundamental concepts every JavaScript developer must master is how to declare variables. JavaScript offers three keywords for variable declaration: var, let, and const. While they might seem similar at first glance, each has distinct characteristics that can significantly impact your code's behavior and reliability.
In this guide, we'll explore the differences between these three declaration types, provide practical examples, and discuss the drawbacks and cautions you should be aware of when using each.
The Evolution of JavaScript Variables
Before ES6 (ECMAScript 2015), var was the only way to declare variables in JavaScript. However, its quirky behavior led to bugs and confusion. ES6 introduced let and const to address these issues, providing developers with more predictable and safer alternatives.
var: The Legacy Approach
Key Characteristics
- Function-scoped (or globally scoped if declared outside a function)
- Can be redeclared and updated within the same scope
- Hoisted to the top of its scope and initialized with
undefined - Creates a property on the global object when declared globally
function varExample() {
console.log(x); // undefined (hoisted)
var x = 5;
if (true) {
var x = 10; // Same variable!
console.log(x); // 10
}
console.log(x); // 10 (not block-scoped)
}
var globalVar = "I'm global";
console.log(window.globalVar); // "I'm global" (in browsers)
Cautions
- No block scope leads to unexpected behavior in loops and conditionals
- Hoisting can cause confusion and hard-to-debug issues
- Easy to accidentally redeclare variables, overwriting values unintentionally
- Pollutes the global namespace when declared globally
- Generally considered outdated β modern code should avoid
var
let: Block-Scoped and Reassignable
Key Characteristics
- Block-scoped (confined to
{}blocks) - Cannot be redeclared in the same scope
- Can be updated after declaration
- Hoisted but not initialized (temporal dead zone)
function letExample() {
// console.log(y); // ReferenceError: Cannot access before initialization
let y = 5;
if (true) {
let y = 10; // Different variable (block-scoped)
console.log(y); // 10
}
console.log(y); // 5 (outer scope maintained)
// Perfect for loops
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // Prints 0, 1, 2 correctly
}
}
Drawbacks & Cautions
- Temporal dead zone can catch you off guard if you try to access the variable before its declaration
- Still mutable, which can lead to bugs if you intended the value to remain constant
- Requires discipline to avoid unintended reassignments in complex code
const: Immutable References
Key Characteristics
- Block-scoped like
let - Cannot be redeclared or reassigned
- Must be initialized at declaration
- For objects/arrays, the reference is constant but contents can be modified
function constExample() {
const z = 5;
// z = 10; // TypeError: Assignment to constant variable
const user = { name: "Alice" };
user.name = "Bob"; // β
This works! (modifying contents)
// user = {}; // β TypeError: Assignment to constant variable
const arr = [1, 2, 3];
arr.push(4); // β
This works!
// arr = []; // β TypeError: Assignment to constant variable
const PI = 3.14159; // Perfect use case
}
Cautions
- Can be misleading with objects and arrays β the contents are still mutable.
- Cannot declare without initialization β
const x;is invalid - If you need to reassign later, you must use
letinstead - Use
Object.freeze()if you need true immutability for objects:
const config = Object.freeze({ API_KEY: "abc123" });
config.API_KEY = "new"; // Fails silently (or throws error in strict mode)
Comparison Table
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function/Global | Block | Block |
| Redeclaration | β Allowed | β Not allowed | β Not allowed |
| Reassignment | β Allowed | β Allowed | β Not allowed |
| Hoisting | Yes (initialized as undefined) | Yes (not initialized) | Yes (not initialized) |
| Temporal Dead Zone | No | Yes | Yes |
| Global Object Property | Yes | No | No |
Best Practices for Modern JavaScript
Following these conventions will make your code more predictable, maintainable, and less prone to bugs:
1. Default to const
Use const for any value that won't be reassigned. This makes your code's intent clear and prevents accidental modifications.
const MAX_USERS = 100;
const API_ENDPOINT = "https://api.example.com";
2. Use let when reassignment is necessary
Only reach for let when you know the value will change.
let counter = 0;
for (let i = 0; i < 10; i++) {
counter += i;
}
3. Avoid var entirely in new code
There's no modern use case where var is preferable to let or const.
4. Consider immutability for objects
If you need truly immutable data structures, consider using libraries like Immutable.js or the native Object.freeze().
Real-World Example
Here's how you might apply these principles in a practical scenario:
function processUserData(userId) {
const API_URL = "https://api.example.com/users"; // Won't change
let userData = null; // Will be reassigned
try {
const response = fetch(`${API_URL}/${userId}`);
userData = response.json();
// Process data
for (let i = 0; i < userData.posts.length; i++) {
console.log(userData.posts[i].title);
}
} catch (error) {
console.error("Failed to fetch user data:", error);
}
return userData;
}
Conclusion
Understanding the differences between var, let, and const is crucial for writing clean, predictable JavaScript code. While var served its purpose in the early days of JavaScript, modern development should embrace const as the default choice, falling back to let only when reassignment is necessary.
By following these best practices, you'll write code that's easier to understand, debug, and maintainβboth for yourself and for other developers who work with your code.
Key Takeaways
β
Use const by default
β
Use let when you need to reassign
β
Avoid var in modern JavaScript
β
Remember: const doesn't make objects immutable, only the reference
π. Similar posts
Mastering Vue 3 Template Syntax: A Complete Guide
09 Feb 2026
What is Hoisting in JavaScript
08 Feb 2026
What is ECMAScript
08 Feb 2026