2.1 Variable Declarations — var, let, const
What is a Variable?
A variable is a named memory space that stores data. In JavaScript, you can declare variables using three keywords: var, let, and const.
var oldWay = "Pre-ES5 style";
let modernVar = "Reassignable variable";
const immutable = "Constant — cannot be reassigned";
var: The Old Way
Characteristics
- Function scope: Accessible throughout the function it's declared in
- Re-declaration allowed: Can be declared multiple times with the same name
- Reassignment allowed: Value can be changed
- Hoisting: Declaration is moved to the top of its scope (value is
undefined)
// Function scope
function example() {
if (true) {
var x = 10; // Function scope, not block scope
}
console.log(x); // 10 (accessible outside the block!)
}
// Re-declaration allowed
var name = "Alice";
var name = "Bob"; // No error
console.log(name); // "Bob"
// In global scope, var becomes a property of window (browser)
var globalVar = "I'm global";
console.log(window.globalVar); // "I'm global"
Problems with var
// 1. Unintended global variable
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 5 5 5 5 5 (i is 5 after loop ends)
// Expected: 0 1 2 3 4
// 2. Accessible before declaration (confusing hoisting)
console.log(hoisted); // undefined (not an error!)
var hoisted = "value";
let: Block-scoped Variable
Characteristics
- Block scope: Valid only within
{}block - No re-declaration: Cannot re-declare in same scope
- Reassignment allowed: Value can be changed
- TDZ: Temporal Dead Zone — cannot access before declaration
// Block scope
function example() {
if (true) {
let x = 10; // Block scope
console.log(x); // 10
}
console.log(x); // ReferenceError: x is not defined
}
// Correct behavior in for loops
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 0 1 2 3 4 (new i for each iteration)
// No re-declaration
let name = "Alice";
let name = "Bob"; // SyntaxError: Identifier 'name' has already been declared
const: Constants (No Reassignment)
Characteristics
- Block scope: Same as let
- No re-declaration: Same as let
- No reassignment: Must initialize at declaration, cannot reassign afterward
- Object/array internals can be modified: Reference is fixed, contents can change
// No reassignment
const PI = 3.14159;
PI = 3; // TypeError: Assignment to constant variable
// Must initialize at declaration
const x; // SyntaxError: Missing initializer in const declaration
// Object internals CAN be modified!
const user = { name: "Alice", age: 25 };
user.name = "Bob"; // OK!
user.age = 30; // OK!
console.log(user); // { name: "Bob", age: 30 }
// Arrays too
const arr = [1, 2, 3];
arr.push(4); // OK!
arr[0] = 10; // OK!
console.log(arr); // [10, 2, 3, 4]
// Creating a truly immutable object: Object.freeze
const frozen = Object.freeze({ x: 1, y: 2 });
frozen.x = 100; // Silently ignored (TypeError in strict mode)
console.log(frozen.x); // 1 (unchanged)
Understanding Hoisting
Hoisting is the behavior where the JavaScript engine moves variable/function declarations to the top of their scope before code execution.
var Hoisting
// Actual code
console.log(x); // undefined (not an error!)
var x = 5;
console.log(x); // 5
// How JavaScript actually interprets it
var x; // Declaration hoisted (value is undefined)
console.log(x); // undefined
x = 5; // Assignment stays in place
console.log(x); // 5
TDZ (Temporal Dead Zone)
The Temporal Dead Zone is the period where let and const variables cannot be accessed before their declaration.
// TDZ example
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 5;
// The TDZ zone
{
// ← TDZ starts (before let/const declaration)
console.log(tdz); // ReferenceError!
let tdz = "value"; // ← TDZ ends (declaration complete)
console.log(tdz); // "value"
}
Practical Pattern: When to Use What
// Rule 1: Use const by default
const MAX_SIZE = 100;
const config = { debug: false, timeout: 3000 };
const users = [];
// Rule 2: Use let only when reassignment is needed
let count = 0;
count++;
let currentUser = null;
currentUser = fetchUser();
// Rule 3: Never use var
// var should only appear in legacy code
// Pattern: loops
for (const item of items) {
// const receives each iteration's value
console.log(item);
}
for (let i = 0; i < 10; i++) {
// let when index modification is needed
}
Pro Tips
Why const Makes Code Safer
// const prevents unintended reassignment
const API_URL = 'https://api.example.com';
// API_URL = 'https://evil.com'; // TypeError: caught immediately!
// Good to also declare functions as const
const calculateTax = (amount) => amount * 0.1;
// calculateTax = () => 0; // Prevents unintended changes
// For complete immutability, use Object.freeze or immutability libraries
import { produce } from 'immer'; // Deep immutability library