Last updated: Apr 12, 2025
Table of Contents
Introduction: Declaring Variables in JavaScript
Variables are fundamental building blocks in any programming language, acting as named containers for storing
data values. In JavaScript, how you declare a variable significantly impacts its behavior, especially
concerning its scope (where it’s accessible) and whether its value can be changed. Before ES6 (ECMAScript
2015), var was the only way to declare variables. ES6 introduced let and const, providing more control
and helping developers write cleaner, less error-prone code.
Understanding var (The Old Way)
The var keyword was the original way to declare variables in JavaScript. It has some quirks that can lead
to unexpected behavior in modern development.
Scope
-
Scope:
varvariables are eitherglobally scoped(if declared outside
any function) orfunction scoped(if declared inside a function). They arenotblock-scoped (e.g., insideifstatements orforloops). -
Hoisting:
vardeclarations are “hoisted” to the top of their scope (global or function)
during compilation. This means the variable exists throughout the scope, but it’s initialized with
undefineduntil its declaration line is reached. -
Re-declaration:You can re-declare the same variable using
varwithin the same scope
without errors. -
Re-assignment:You can update the value of a
varvariable. -
Global Object Property:When declared in the global scope (outside functions),
var
creates a property on the global object (windowin browsers,globalin Node.js).However, this does not apply to variables declared in modules (files usingimport/export). -
Global Object Property Exception:Note that
vardeclarations in ES modules (files using
import/export)
do not create properties on the global object, even at the top level.
Example (var Scope):
function varScopeTest() {
if (true) {
var message = "Hello from inside if";
}
console.log(message); // Output: "Hello from inside if" - message leaks outside the block
}
varScopeTest();
// console.log(message); // ReferenceError: message is not defined (outside the function scope)
Hoisting
Example (var Hoisting):
console.log(myVar); // Output: undefined (hoisted but not yet assigned)
var myVar = 10;
console.log(myVar); // Output: 10
Re-declaration
Example (var Re-declaration):
var x = 5;
console.log(x); // Output: 5
var x = 20; // No error
console.log(x); // Output: 20
Due to these behaviors, especially the lack of block scope, var is generally avoided in modern JavaScript
development in favor of let and const.
Introducing let (The Modern Mutable Choice)
Introduced in ES6, let provides a more predictable way to declare variables whose values might need to
change later.
Block Scope
-
Scope:
letvariables areblock-scoped. They only exist within the
block ({...}) in which they are declared (e.g., inside anif,forloop, or just a standalone block). -
Hoisting & Temporal Dead Zone (TDZ):While
letandconstdeclarations are hoisted,
their TDZ prevents any access before declaration. Attempting to access the variable before its declaration
will result in aReferenceError. This differs fromvarwhich returnsundefinedwhen accessed before
declaration. -
Re-declaration:You cannot re-declare the same variable using
letwithin the same
scope. -
Re-assignment:You can update (reassign) the value of a
letvariable. -
Global Object Property:
letdoes not create a property on the global object when
declared in the global scope.
Example (let Scope):
function letScopeTest() {
if (true) {
let blockMessage = "Hello from inside if";
console.log(blockMessage); // Output: "Hello from inside if"
}
// console.log(blockMessage); // ReferenceError: blockMessage is not defined (outside the block scope)
}
letScopeTest();
Temporal Dead Zone (TDZ)
Example (let TDZ):
// console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization (TDZ)
let myLet = 30;
console.log(myLet); // Output: 30
Re-declaration Rules
Example (let Re-declaration):
let y = 15;
console.log(y); // Output: 15
// let y = 25; // SyntaxError: Identifier 'y' has already been declared
Use let when you need a variable whose value will change over time, like loop counters or temporary
assignments.
Understanding const (For Constant References)
const (also introduced in ES6) is used to declare variables whose values are intended to remain constant.
However, there’s an important nuance regarding objects and arrays.
Working with Objects
-
Scope:
constvariables areblock-scoped, just likelet. -
Hoisting & Temporal Dead Zone (TDZ):
constdeclarations are hoisted but not
initialized, creating a TDZ, just likelet. Accessing before declaration results in aReferenceError. -
Re-declaration:You cannot re-declare the same variable using
constwithin the same
scope, just likelet. -
Re-assignment:Youcannotreassign a
constvariable after it’s
declared. This is its primary characteristic. -
Initialization:
constvariablesmustbe initialized with a value when
they are declared. -
Global Object Property:
constdoes not create a property on the global object. -
Important Nuance (Objects/Arrays):
constmakes thebinding(the variable name)
constant, not necessarily thevalueit holds. If aconstvariable holds an object or an array,
you cannot reassign the variable to a new object or array, but you can modify the properties of the
object or the elements of the array. -
Object Immutability: If you need true immutability for an object declared with
const,
you can use
Object.freeze():
```javascript
const frozenPerson = Object.freeze({ name: “Alice” });
frozenPerson.name = “Bob”; // This will fail silently in non-strict mode
// or throw TypeError in strict mode -
Using
constwith Destructuring: When using destructuring assignment withconst, each
destructured variable becomes a constant:
### Destructuringconst { name, age } = person; name = "Bob"; // TypeError: Assignment to constant variable // However, if person is an object: person.name = "Bob"; // This works as we're modifying the object, not reassigning the const -
Object Properties: While
Object.freeze()can prevent modifications to an object’s
direct properties,
it only creates a shallow freeze. Nested objects can still be modified unless they are also frozen:
### Object.freeze()const person = Object.freeze({ name: "Alice", address: { city: "London" } }); person.name = "Bob"; // Fails (frozen) person.address.city = "Paris"; // Works (nested object not frozen)
Example (const Re-assignment):
const PI = 3.14159;
console.log(PI); // Output: 3.14159
// PI = 3.14; // TypeError: Assignment to constant variable.
Example (const Initialization):
// const GREETING; // SyntaxError: Missing initializer in const declaration
const GREETING = "Hello";
Example (const with Objects):
const person = { name: "Alice", age: 30 };
console.log(person); // Output: { name: 'Alice', age: 30 }
// This is allowed: Modifying the object's property
person.age = 31;
console.log(person); // Output: { name: 'Alice', age: 31 }
// This is NOT allowed: Reassigning the constant variable
// person = { name: "Bob", age: 40 }; // TypeError: Assignment to constant variable.
Use const by default for all declarations. This signals that the variable’s assignment shouldn’t change,
which makes code easier to reason about and prevents accidental reassignments.
Additional Scoping Considerations
When working with ES modules, there’s an additional scope to consider:
-
Module Scope:Variables declared at the top level of a module (file) are scoped to that
module, regardless of whether you usevar,let, orconst. -
Import/Export:You can only export variables that are declared at the module scope level.
// moduleA.js
export const config = { api: 'example.com' };
var helper = 'utility'; // Only accessible within this module
export { helper }; // Can still export it explicitly
// moduleB.js
import { config, helper } from './moduleA.js';
console.log(config.api); // Works
console.log(window.helper); // undefined - not added to global object
Key Differences Summarized
Feature
var
let
const
Scope
Function or Global
Block
Block
Hoisting
Hoisted & Initialized (undefined)
Hoisted & Not Initialized (TDZ)
Hoisted & Not Initialized (TDZ)
Re-declaration (same scope)
Allowed
Not Allowed
Not Allowed
Re-assignment
Allowed
Allowed
Not Allowed
Must Be Initialized
No
No
Yes
Global Object Property
Yes (in global scope)
No
No
When to Use Which?
The modern best practice in JavaScript (ES6+) is straightforward:
Use const by default: Start by declaring all your variables with const. This makes
your code more predictable as it guarantees the variable binding will not be reassigned.
Use let only if you need to reassign the variable: If you know a variable’s value needs
to change during its lifecycle (e.g., a loop counter, a state variable that gets updated), then use let.
Avoid var: There is generally no reason to use var in modern JavaScript codebases.
let and const offer better scoping rules and help prevent common bugs associated with var.
Consider block scope for temporary variables: Use blocks (even without control
statements) to limit
the scope of temporary variables:
{
const temp = heavyComputation();
// temp only exists in this block
doSomethingWith(temp);
}
By following these guidelines, you can write cleaner, more maintainable, and less error-prone JavaScript
code.