💡 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
var
creates function-scoped variables with unpredictable behavior, whilelet
andconst
create block-scoped variables that are safer and more predictable.let
allows reassignment,const
prevents it. Modern JavaScript developers useconst
by default andlet
when reassignment is needed, avoidingvar
entirely.
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 overlet
andconst
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
var
withconst
first - Start by changing allvar
declarations 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 withconst
for every variable and only change tolet
if you need reassignment - Use
let
for reassignment - When a variable’s value needs to change,let
provides block scope withoutvar
’s problems - Avoid
var
entirely - It has unpredictable scoping behavior and no advantages over modern alternatives - Think about scope - Block-scoped variables (
let
andconst
) 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: In our next article, we’ll explore the differences between JavaScript’s primitive and reference types, and how each type handles copying and assignment. Understanding these concepts will deepen your knowledge of how variables actually work under the hood.
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?