When working with JavaScript, understanding how variables are declared is crucial to writing clean, bug-free code. JavaScript provides multiple ways to declare variables: using var
, let
, const
, or even without any keyword. Each has different behaviors, scoping rules, and best-use cases.
This article breaks down the key differences between them, providing real-world examples, potential pitfalls, and best practices.
No Keyword Variable Declaration
When you assign a value to a variable without declaring it with var
, let
, or const
, JavaScript treats it differently based on whether strict mode is enabled.
Behavior in Non-Strict Mode
If the variable has not been declared previously, JavaScript assigns it as a property of the global object (window
in browsers, global
in Node.js).
x = 10; // No keyword
console.log(x); // 10
console.log(window.x); // 10 (in browsers)
This approach is not recommended because:
– It makes debugging harder, as undeclared variables can accidentally pollute the global scope.
– It can lead to unexpected behavior when multiple scripts are running.
Behavior in Strict Mode
In strict mode, undeclared variables throw an error instead of being automatically assigned to the global object.
"use strict";
x = 10; // ReferenceError: x is not defined
Key Takeaways
✅ Avoid using variables without a declaration keyword.
✅ Always declare variables explicitly with var
, let
, or const
.
var
: The Legacy Variable Declaration
Before ES6 (2015), var
was the only way to declare variables in JavaScript. It still works today, but it comes with some drawbacks.
Characteristics of var
- Function-scoped: It is accessible anywhere within the function where it is declared.
- Hoisted: It is moved to the top of its function scope during execution.
- Can be redeclared: You can declare the same variable multiple times in the same scope.
Example of var
Scope Issue
Consider the following loop:
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
Output:
3
3
3
This happens because var
is function-scoped, meaning all setTimeout
callbacks refer to the same i
, which has already reached 3
when they execute.
Fixing It
A workaround using an IIFE (Immediately Invoked Function Expression):
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(() => {
console.log(j);
}, 1000);
})(i);
}
✅ Use let
instead (see next section).
let
: Block-Scoped and More Predictable
Introduced in ES6 (2015), let
solves the scoping issues of var
.
Characteristics of let
- Block-scoped: It is accessible only within the block
{}
where it is declared. - Hoisted but in the Temporal Dead Zone (TDZ): It cannot be accessed before its declaration.
- Cannot be redeclared in the same scope.
Example of let
Fixing the var
Issue
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
Output:
0
1
2
Since let
is block-scoped, each loop iteration has a new i
variable, solving the issue.
const
: Block-Scoped and Immutable References
const
is also introduced in ES6 and should be used for values that should not be reassigned.
Characteristics of const
- Block-scoped (same as
let
). - Hoisted but in the Temporal Dead Zone.
- Cannot be reassigned after declaration.
Example
const name = "John";
name = "Doe"; // TypeError: Assignment to constant variable
However, objects and arrays declared with const
can be mutated:
const user = { name: "John" };
user.name = "Doe"; // Allowed
console.log(user.name); // Doe
user = {}; // TypeError: Assignment to constant variable
✅ Use const
as the default unless reassignment is required.
Hoisting: var
vs let
vs const
All three are hoisted, but behave differently:
console.log(a); // undefined
var a = 5;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;
console.log(c); // ReferenceError: Cannot access 'c' before initialization
const c = 15;
var
is hoisted but initialized asundefined
.let
andconst
are hoisted but remain in the Temporal Dead Zone (TDZ) until their declaration.
Best Practices Summary
Feature | var |
let |
const |
---|---|---|---|
Scope | Function-scoped | Block-scoped | Block-scoped |
Hoisting | Yes (initialized as undefined ) |
Yes (TDZ applies) | Yes (TDZ applies) |
Redeclaration | Allowed | Not allowed | Not allowed |
Reassignment | Allowed | Allowed | Not allowed |
Use Case | Avoid | When reassignment is needed | Default for all variables |
✅ Use const
by default.
✅ Use let
when reassignment is necessary.
❌ Avoid var
unless necessary for legacy support.
❌ Never declare variables without a keyword.
Conclusion
Understanding the differences between var
, let
, const
, and undeclared variables helps write cleaner, more maintainable JavaScript. By following best practices:
- Prefer
const
for all variables unless you need reassignment. - Use
let
if reassignment is required. - Avoid
var
due to scoping issues. - Never declare variables without a keyword.
By mastering these concepts, you can prevent bugs, write more predictable code, and improve your JavaScript skills.
📌 Next Steps
- ✅ Try rewriting old
var
code usinglet
orconst
. - ✅ Experiment with hoisting and the TDZ to understand their effects.
- ✅ Check out MDN’s documentation on
let
for more details.
Got questions? Drop them in the comments! 🚀