Stop guessing and start coding with clarity — here’s when to use each variable declaration in modern JavaScript.

Introduction
You’ve probably seen this question asked in every JavaScript interview and tutorial:
👉 What’s the difference between var
, let
, and const
?
At first glance, it seems simple:
var
is old.let
is new.const
means constant.
But when you actually start coding, things get messy:
- Why does
var
behave differently inside loops? - Why does
const
allow object mutation? - Why does
let
throw errors whilevar
doesn’t?
The truth is, each keyword has distinct behavior, scope rules, and use cases. Misusing them can lead to subtle bugs — the kind that take hours to debug.
In this post, we’ll break it down step by step, with real-world examples, pitfalls, and best practices so you’ll know exactly which one to use and when.
1. Why Three Keywords Exist
JavaScript didn’t always have three.
- Before ES6 (2015): Only
var
existed. It worked, but had quirks. - After ES6:
let
andconst
were introduced to fix scoping issues and make code more predictable.
👉 Rule of thumb today: In modern code, you almost never need var
. But knowing it is essential because:
- Legacy codebases still use it.
- It explains many JavaScript quirks (like hoisting).
2. Scoping Rules: The Big Divide
var → Function Scope
function testVar() {
if (true) {
var message = "Hello, var!";
}
console.log(message); // ✅ Works
}
testVar();
Even though message
is inside an if
block, it “escapes” — because var
is function-scoped, not block-scoped.
let & const → Block Scope
function testLetConst() {
if (true) {
let msg1 = "Hello, let!";
const msg2 = "Hello, const!";
}
console.log(msg1); // ❌ ReferenceError
console.log(msg2); // ❌ ReferenceError
}
testLetConst();
Both are block-scoped → safer, more predictable.
👉 Real-world effect:
When looping over arrays, using var
can cause unexpected leaks. Using let
/const
keeps variables tightly scoped.
3. Hoisting: The Gotcha
Hoisting means “moving declarations to the top of scope” during compilation. But not all declarations are equal.
console.log(a); // undefined
var a = 10;
console.log(b); // ❌ ReferenceError (TDZ)
let b = 20;
console.log(c); // ❌ ReferenceError (TDZ)
const c = 30;
var
→ Hoisted & initialized withundefined
.let
/const
→ Hoisted but uninitialized → fall into the Temporal Dead Zone (TDZ) until declaration.
👉 Pro tip: Always declare variables at the top of their scope to avoid confusion.
4. Reassignment & Redeclaration
var
- Can be reassigned.
- Can be redeclared in the same scope.
var x = 1;
var x = 2; // ✅ Allowed
x = 3; // ✅ Allowed
let
- Can be reassigned.
- Cannot be redeclared in the same scope.
let y = 1;
y = 2; // ✅ Allowed
let y = 3; // ❌ SyntaxError
const
- Cannot be reassigned.
- Cannot be redeclared.
- ⚠️ But objects/arrays declared with
const
are mutable.
const z = 1;
z = 2; // ❌ TypeError
const obj = { name: "Alice" };
obj.name = "Bob"; // ✅ Allowed (reference unchanged)
👉 Gotcha: const
locks the binding, not the contents.
5. Real-World Example: Loops
With var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// Logs: 3, 3, 3
All callbacks share the same i
, which ends at 3
.
With let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// Logs: 0, 1, 2
let
creates a new binding per iteration → closures work correctly.
👉 Impact: This fixes one of the most common bugs in async loops.
6. When to Use var
Use var
only when:
- Maintaining legacy code.
- You explicitly need function-scoped variables.
- You’re targeting environments without ES6 support.
Otherwise → avoid it.
7. When to Use let
Use let
when:
- The variable will be reassigned.
- Example: Loop counters, mutable state.
let total = 0;
for (let i = 1; i <= 5; i++) {
total += i;
}
console.log(total); // 15
8. When to Use const
Use const
as your default.
- Most variables don’t need reassignment.
- Signals intent: “this won’t change.”
- Safer in large codebases.
const API_URL = "https://api.example.com";
const user = { id: 1, name: "Rahmat" };
👉 Functional programming patterns especially love const
.
9. Best Practices
- Prefer const. Downgrade to
let
if reassignment needed. - Avoid var. Consider it legacy.
- Use ESLint/Prettier rules to enforce:
"rules": {
"no-var": "error",
"prefer-const": "error"
}
- Think intent, not habit. Always ask: Should this ever change?
10. Quick Decision Tree
- Need reassignment? → use
let
. - Never reassigned? → use
const
. - Old-school or function-scope needed? → use
var
.
That’s it.
11. Real Project Case Study
I once reviewed a Node.js service where a developer used var
for loop counters inside async database calls. Guess what happened?
- Instead of updating 3 different records, all callbacks referenced the same final value of
var i
. - Fix was as simple as changing
var
→let
. - Saved hours of debugging.
👉 Sometimes, the difference is more than academic — it’s production bugs.
Conclusion
We covered a lot, but the essence is simple:
var
→ function-scoped, legacy, avoid unless necessary.let
→ block-scoped, reassignable.const
→ block-scoped, not reassignable (but contents can mutate).
👉 Pro Tip: Start with const
. If you realize reassignment is required, switch to let
. Forget var
unless you’re in a museum project.
Call to Action
What’s your team’s convention for var
vs let
vs const
?
💬 Share your thoughts in the comments.
🔖 Bookmark this for future interviews and code reviews.
👩💻 Share with that teammate who still writes var
in 2025.
Leave a Reply