Skip to main content
Advertisement

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
Advertisement