💡 If you’re familiar with JavaScript Scope, you’ll find this article much easier to follow.
When you’re starting with JavaScript, you quickly discover there are three different ways to declare variables: var, let, and const. Confusing, right?
I remember my early days as a JavaScript developer, wondering why we even need three different keywords for something as simple as creating a variable. I used var for everything because it seemed to work just fine. But then I ran into weird bugs where variables had unexpected values, loops behaved strangely, and my code became harder to debug. Everything changed when I realized that var has some quirky behaviors that can trip up even experienced developers.
In this article, I’ll show you exactly why modern JavaScript developers avoid var and when to choose let vs const instead. We’ll cover everything from the fundamental differences between these declarations to practical strategies for writing cleaner, more predictable code.
What’s the difference between var, let, and const?
The TL;DR: JavaScript Variable Declarations
varcreates function-scoped variables with unpredictable behavior, whileletandconstcreate block-scoped variables that are safer and more predictable.letallows reassignment,constprevents it. Modern JavaScript developers useconstby default andletwhen reassignment is needed, avoidingvarentirely.
Why var Creates Unexpected Problems
The Function Scope Problem
var creates function-scoped variables, not block-scoped ones. This means the variable exists throughout the entire function, not just within the block where it’s declared.
Here’s where this gets confusing:
| |
In most programming languages, you’d expect message to only exist inside the if block. But with var, it’s accessible throughout the entire function. This behavior makes code harder to understand and debug.
In a production environment, this can lead to accidentally overwriting variables or accessing data from the wrong scope.
The Loop Nightmare
Here’s a classic example that breaks many developers’ expectations:
| |
You’d expect this to log “Button 0 clicked”, “Button 1 clicked”, “Button 2 clicked”. Instead, it logs “Button 3 clicked” three times!
Why does this happen? The var i variable is shared across all iterations because it’s function-scoped. By the time the setTimeout callbacks run, the loop has finished and i equals 3.
The Hoisting Confusion
Hoisting is JavaScript’s behavior of moving variable declarations to the top of their scope during compilation. With var, this creates confusing situations:
| |
The JavaScript engine treats this code as if you wrote:
| |
While this doesn’t crash your program, it can mask logic errors. You might think a variable isn’t declared when it actually is—it just doesn’t have a value yet.
For a deeper dive into how hoisting works, check out our detailed guide on JavaScript hoisting.
let: Block-Scoped and Predictable
let was introduced in ES6 to solve the problems with var. It creates block-scoped variables that behave more predictably.
How let Fixes the Scope Problem
| |
With let, the variable only exists within the block where it’s declared. This makes your code more predictable and prevents accidental access to variables outside their intended scope.
Solving the Loop Problem
Remember our broken loop example? Here’s how let fixes it:
| |
Why this works: let creates a new binding for each iteration of the loop. Each iteration gets its own copy of i, so the callbacks capture the correct value.
Preventing Redeclaration Mistakes
| |
This immediate feedback during development helps catch typos and prevents accidentally creating duplicate variables.
let and Hoisting
Unlike var, variables declared with let are hoisted but not initialized. This creates a “temporal dead zone”:
| |
This behavior is actually helpful—it forces you to declare variables before using them, preventing many common bugs.
const: For Values That Don’t Change
const creates variables that cannot be reassigned after declaration. It’s perfect for values that should remain constant throughout your program.
Basic const Usage
| |
The Important Rule: Immediate Assignment Required
| |
You must assign a value to const variables when you declare them. This requirement helps prevent bugs where variables might be used before they’re properly initialized.
const with Objects and Arrays
Here’s where const gets interesting—it prevents reassignment of the variable, but not modification of the object’s contents:
| |
Think of const as creating a permanent pointer to a container. You can change what’s inside the container, but you can’t point to a different container.
const with Arrays
The same principle applies to arrays:
| |
When to Use Each Declaration Type
Step-by-Step Decision Guide
Follow this decision process every time you declare a variable:
- Start with
const- Ask yourself: “Will this variable need to be reassigned?” - If no reassignment needed - Use
const(most common case) - If reassignment is needed - Use
let - Never use
var- It has no advantages overletandconst
Practical Examples: const vs let
Use const for:
| |
Use let for:
| |
Common Mistake: Using let When const Would Work
| |
Remember: You’re not reassigning the items variable—you’re modifying its contents. const is the right choice here.
Comparison: var vs let vs const
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function-scoped | Block-scoped | Block-scoped |
| Reassignment | ✅ Allowed | ✅ Allowed | ❌ Not allowed |
| Redeclaration | ✅ Allowed (problematic) | ❌ Not allowed | ❌ Not allowed |
| Hoisting behavior | Hoisted and initialized with undefined | Hoisted but not initialized (temporal dead zone) | Hoisted but not initialized (temporal dead zone) |
| Initialization | Optional at declaration | Optional at declaration | Required at declaration |
| Best practice | ❌ Avoid completely | ✅ Use when reassignment needed | ✅ Use by default |
Migrating from var to Modern Declarations
Step-by-Step Migration Process
If you’re working with legacy code that uses var, here’s how to safely migrate:
- Replace
varwithconstfirst - Start by changing allvardeclarations toconst - Fix assignment errors - If you get “Assignment to constant variable” errors, change those specific cases to
let - Check for scope issues - Test thoroughly to ensure no functionality breaks due to scope changes
- Update loop variables - Pay special attention to loop counters and variables used in callbacks
Migration Example
| |
Frequently Asked Questions
Should I ever use var in modern JavaScript?
No, there’s no reason to use var in modern JavaScript. let and const provide all the functionality of var with better, more predictable behavior. Even when maintaining legacy code, it’s worth updating var declarations to improve code reliability.
When should I choose let over const?
Use let when you need to reassign the variable after its initial declaration. Common cases include loop counters, variables that get different values based on conditions, and accumulator variables. If you find yourself using let everywhere, step back and see if some of those variables could be const instead.
Can I modify arrays and objects declared with const?
Yes! const prevents reassignment of the variable itself, not modification of its contents. You can push to arrays, change object properties, and call any methods that modify the data structure. You just can’t assign a completely new array or object to the variable.
What happens if I try to use a let or const variable before declaring it?
You’ll get a ReferenceError. Unlike var, which gets hoisted and initialized with undefined, let and const are in a “temporal dead zone” until their declaration is reached. This actually helps catch bugs early in development.
Is there a performance difference between var, let, and const?
The performance difference is negligible in modern JavaScript engines. Any micro-optimizations are far outweighed by the benefits of cleaner, more maintainable code. Focus on writing predictable code rather than worrying about tiny performance differences.
Key Takeaways
Here are the most important points to remember about JavaScript variable declarations:
- Default to
const- Start withconstfor every variable and only change toletif you need reassignment - Use
letfor reassignment - When a variable’s value needs to change,letprovides block scope withoutvar’s problems - Avoid
varentirely - It has unpredictable scoping behavior and no advantages over modern alternatives - Think about scope - Block-scoped variables (
letandconst) are easier to reason about than function-scoped ones (var)
Conclusion
Making the switch from var to let and const isn’t just about following modern JavaScript trends—it’s about writing code that behaves predictably and fails fast when something goes wrong.
By defaulting to const and reaching for let when you need mutability, you’ll write cleaner code that’s easier to debug and maintain. Your future self will thank you when you’re not hunting down mysterious bugs caused by variable scope issues.
What’s Next: Now that you understand how to properly declare variables with let and const, it’s time to dive deeper into how JavaScript actually stores and handles different types of data. In our next article, we’ll explore JavaScript Primitive vs Reference Types - a fundamental concept that explains why copying an array sometimes changes the original, and why identical objects compare as false. Understanding these memory management concepts will transform how you think about variable assignment and help you avoid some of the most confusing bugs in JavaScript development.
Ready to modernize your variable declarations? Try building a small project using only let and const—you’ll be surprised how much clearer your code becomes! What’s your experience with JavaScript variable declarations? Have you encountered any tricky bugs with var that let or const would have prevented?
