Back to Blog
Tutorial2025-01-08

let vs const vs var: The Complete Guide to JavaScript Variables

Master the differences between let, const, and var. Learn about hoisting, scope, and when to use each declaration.

The History: Why Three Ways?

JavaScript originally only had var. It worked, but had some confusing behaviors that led to bugs. ES6 (2015) introduced let and const to fix these issues. Understanding all three is crucial because you'll encounter var in legacy code.

The Core Differences at a Glance

| Feature | var | let | const | |---------|-----|-----|-------| | Scope | Function | Block | Block | | Hoisting | Yes (undefined) | Yes (TDZ) | Yes (TDZ) | | Reassignment | Yes | Yes | No | | Redeclaration | Yes | No | No |

Let's explore what each of these means.

Scope: Where Variables Live

var: Function Scope

var is scoped to the nearest function, not the nearest block. This leads to surprising behavior:

``javascript function example() { if (true) { var message = 'Hello'; } console.log(message); // 'Hello' - still accessible! }

// In a loop, this can cause bugs for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Output: 3, 3, 3 (not 0, 1, 2!) `

The loop example is a classic JavaScript gotcha. By the time the setTimeout callbacks run, the loop has finished and i is 3.

let and const: Block Scope

let and const are scoped to the nearest block (anything between {}):

`javascript function example() { if (true) { let message = 'Hello'; const greeting = 'Hi'; } console.log(message); // ReferenceError! console.log(greeting); // ReferenceError! }

// The loop works correctly with let for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Output: 0, 1, 2 (correct!) `

Each iteration of the loop gets its own i variable.

Hoisting: The Temporal Dead Zone

All three declarations are hoisted (moved to the top of their scope), but they behave differently.

var: Hoisted and Initialized to undefined

`javascript console.log(name); // undefined (not an error!) var name = 'Alice';

// JavaScript sees this as: var name; console.log(name); // undefined name = 'Alice'; `

This can mask bugs because you get undefined instead of an error.

let and const: Hoisted but in the TDZ

`javascript console.log(name); // ReferenceError: Cannot access 'name' before initialization let name = 'Alice'; `

The "Temporal Dead Zone" (TDZ) is the period between entering the scope and the actual declaration. Accessing the variable during this time throws an error.

`javascript // The TDZ in action { // TDZ starts here console.log(typeof myVar); // undefined (var) console.log(typeof myLet); // ReferenceError! (TDZ)

var myVar = 'var'; let myLet = 'let'; // TDZ ends here } `

The TDZ helps catch bugs by making it clear when you're using a variable before it's declared.

Reassignment and Redeclaration

var: Both Allowed

`javascript var count = 1; var count = 2; // No error - redeclaration allowed count = 3; // No error - reassignment allowed `

This can lead to accidental overwrites:

`javascript var user = 'Alice'; // ... 100 lines later ... var user = 'Bob'; // Oops! Accidentally created a new variable with same name `

let: Reassignment Only

`javascript let count = 1; let count = 2; // SyntaxError: Identifier 'count' has already been declared count = 3; // OK - reassignment allowed `

const: Neither Allowed

`javascript const count = 1; const count = 2; // SyntaxError count = 3; // TypeError: Assignment to constant variable `

Important: const doesn't mean the value is immutable, just that the binding can't change:

`javascript const user = { name: 'Alice' }; user.name = 'Bob'; // OK! Object properties can change user = { name: 'Charlie' }; // TypeError! Can't reassign the variable

const numbers = [1, 2, 3]; numbers.push(4); // OK! Array methods work numbers = [5, 6, 7]; // TypeError! Can't reassign `

Best Practices: When to Use Each

Default to const

Use const by default. It signals that the variable won't be reassigned, making code easier to understand:

`javascript const API_URL = 'https://api.example.com'; const user = { name: 'Alice', age: 25 }; const numbers = [1, 2, 3]; `

Use let When Reassignment is Needed

`javascript let count = 0; for (let i = 0; i < 10; i++) { count += i; }

let status = 'pending'; if (condition) { status = 'complete'; } `

Avoid var in Modern Code

There's almost no reason to use var in new code. The only exception might be when you intentionally want function-scoped behavior (rare).

Real-World Patterns

Configuration Objects

`javascript const config = { apiUrl: 'https://api.example.com', timeout: 5000, retries: 3 }; // Properties can be modified, but config can't be reassigned config.timeout = 10000; // OK `

Accumulator Pattern

`javascript const items = [1, 2, 3, 4, 5]; let sum = 0; // Will be reassigned for (const item of items) { // item is new each iteration sum += item; } `

State Management

`javascript let currentUser = null; // Will change

function login(user) { currentUser = user; }

function logout() { currentUser = null; } `

Common Interview Questions

Q: What's the difference between let and const? A: Both are block-scoped. let allows reassignment, const doesn't. Use const by default.

Q: Why does this print 3, 3, 3? `javascript for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } ` A: var is function-scoped, so there's only one i shared across all iterations. By the time callbacks run, the loop is done and i is 3.

Q: Can you modify a const object? A: Yes! const` only prevents reassignment of the variable, not mutation of the value.

Summary

- const: Block-scoped, can't reassign. Use by default. - let: Block-scoped, can reassign. Use when value changes. - var: Function-scoped, can reassign and redeclare. Avoid in new code.

The modern JavaScript rule is simple: const first, let when needed, var never.