Published by Hashan Madhushanka

- 02 min read

Understanding var, let, and const in JavaScript: A Complete Guide

js
Understanding var, let, and const in JavaScript: A Complete Guide

JavaScript offers three ways to declare variables: var, let, and const. While they might seem interchangeable at first glance, understanding their differences is crucial for writing clean, bug-free code. Let’s dive into what makes each one unique and when you should use them.

The Evolution of Variable Declarations

Before ES6 (ECMAScript 2015), var was the only way to declare variables in JavaScript. However, its quirky behavior led to many bugs and confusion. ES6 introduced let and const to address these issues, giving developers more predictable and safer ways to work with variables.

Scope: Where Your Variables Live

var: Function Scope

Variables declared with var are function-scoped, meaning they’re accessible anywhere within the function where they’re declared:


function example() {
  if (true) {
    var x = 10;
  }
  console.log(x); // 10 - accessible outside the if block!
}

This behavior can lead to unexpected results. The variable x “leaks” out of the if block because var doesn’t respect block scope.

Both let and const are block-scoped, meaning they only exist within the nearest set of curly braces:


function example() {
  if (true) {
    let x = 10;
    const y = 20;
  }
  console.log(x); // ReferenceError: x is not defined
  console.log(y); // ReferenceError: y is not defined
}

This behavior is more intuitive and helps prevent bugs by keeping variables contained to their logical scope.

Hoisting: The Invisible Move

var: Hoisted and Initialized

Variables declared with var are hoisted to the top of their scope and initialized with undefined:


console.log(x); // undefined (no error!)
var x = 5;
console.log(x); // 5

This is equivalent to:


var x = undefined;
console.log(x);
x = 5;
console.log(x);

let and const: Temporal Dead Zone

While let and const are also hoisted, they’re not initialized. Accessing them before declaration results in a ReferenceError:


console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 5;

The period between entering scope and the actual declaration is called the Temporal Dead Zone (TDZ). This helps catch errors early in development.

Reassignment: Can You Change It?

var and let: Reassignable

Both var and let allow you to reassign values:


var x = 10;
x = 20; // No problem

let y = 10;
y = 20; // Also fine

const: Immutable Binding

Variables declared with const cannot be reassigned:


const x = 10;
x = 20; // TypeError: Assignment to constant variable

However, it’s important to note that const creates an immutable binding, not an immutable value. For objects and arrays, you can still modify their contents:


const user = { name: 'Alice' };
user.name = 'Bob'; // This works!
user.age = 30; // This also works!

const numbers = [1, 2, 3];
numbers.push(4); // This works!
numbers[0] = 10; // This also works!

// But you can't reassign the entire variable
user = { name: 'Charlie' }; // TypeError!
numbers = [5, 6, 7]; // TypeError!

Redeclaration Rules

var: Allows Redeclaration

You can redeclare the same variable multiple times with var:


var x = 10;
var x = 20; // No error
console.log(x); // 20

This can lead to accidentally overwriting variables in large codebases.

let and const: No Redeclaration

Both let and const prevent redeclaration within the same scope:


let x = 10;
let x = 20; // SyntaxError: Identifier 'x' has already been declared

const y = 10;
const y = 20; // SyntaxError: Identifier 'y' has already been declared

Best Practices: When to Use Each

Use const by Default

Start with const for all declarations. This makes your code more predictable and helps prevent accidental reassignments:


const API_KEY = 'your-api-key';
const MAX_USERS = 100;
const config = { timeout: 5000 };

Use let When Reassignment Is Needed

Only use let when you know the variable will need to be reassigned:


let counter = 0;
counter++;

let result = '';
for (let i = 0; i < 10; i++) {
  result += i;
}

Common Pitfalls and How to Avoid Them

Const Doesn't Mean Immutable

Remember that const only prevents reassignment, not mutation:


const settings = { theme: 'dark' };
settings.theme = 'light'; // This works!

// To truly make it immutable:
const settings = Object.freeze({ theme: 'dark' });
settings.theme = 'light'; // Silently fails (throws error in strict mode)

Conclusion

Understanding the differences between var, let, and const is fundamental to writing modern JavaScript. Here's a quick recap:

  • const: Use by default. Block-scoped, cannot be reassigned, prevents redeclaration.
  • let: Use when reassignment is necessary. Block-scoped, prevents redeclaration.
  • var: Avoid in modern code. Function-scoped, allows redeclaration, hoisted behavior can cause bugs.

By following the pattern of "const by default, let when necessary, var never," you'll write cleaner, more maintainable code that's easier to debug and reason about. Your future self (and your teammates) will thank you for it.