Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
14 minLesson 2 of 35
JavaScript Fundamentals

Variables: var, let & const


title: "Variables: var, let & const" description: "Master JavaScript variable declarations. Understand why var causes bugs, how block scope works with let and const, and how to write predictable, professional code." duration: "35 min" difficulty: "beginner" order: 2

Variables: var, let & const

Variables are named containers for data. JavaScript gives you three ways to declare them: var, let, and const. Choosing the right one is not a stylistic choice — it directly affects whether your code works correctly.

The Problem With var

var was the only way to declare variables in JavaScript before 2015. It works, but it has two behaviors that cause real bugs in production code.

1. var is Function-Scoped, Not Block-Scoped

Scope defines where a variable can be accessed. With var, the boundary is the nearest function. With let and const, the boundary is the nearest block (any pair of {}).

function checkAge(age) {
  if (age >= 18) {
    var message = "Access granted";
  }
  console.log(message); // "Access granted" — leaks out of the if block!
}

checkAge(20);

The if block does not create a new scope for var. The variable leaks into the surrounding function. This is almost never what you want.

With let, the behavior is correct:

function checkAge(age) {
  if (age >= 18) {
    let message = "Access granted";
  }
  console.log(message); // ReferenceError: message is not defined
}

The error is good here. It tells you the code has a logic problem rather than silently using a variable that was never supposed to be visible.

2. var is Hoisted and Initialized to undefined

Hoisting is JavaScript's behavior of moving variable declarations to the top of their scope before any code runs. With var, the declaration is hoisted and the variable is initialized to undefined immediately.

console.log(username); // undefined  (no error, which is misleading)
var username = "alice";
console.log(username); // "alice"

JavaScript internally processes this as:

var username; // hoisted to top, set to undefined
console.log(username); // undefined
username = "alice";
console.log(username); // "alice"

You accessed a variable before you assigned it and got no error. This masks bugs that should fail loudly.

let and const: Block Scope

Introduced in ES2015, let and const both respect block scope. Their declarations are also hoisted, but they are not initialized until the line of code where you declare them. Accessing them before that line throws a ReferenceError. This window is called the Temporal Dead Zone (TDZ).

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

The TDZ is a feature, not a bug. It prevents an entire class of subtle errors that var allows silently.

Block Scope in Practice

for (let i = 0; i < 3; i++) {
  // i is scoped to this block only
}
console.log(i); // ReferenceError: i is not defined

for (var j = 0; j < 3; j++) {
  // j leaks out
}
console.log(j); // 3

This matters enormously in loops that contain asynchronous callbacks — a common source of bugs when using var.

const: Your Default Choice

const declares a variable whose binding cannot be reassigned. Use it for everything that does not need to change.

const PI = 3.14159;
PI = 3; // TypeError: Assignment to constant variable

const Does Not Mean Immutable

This is the most common const misconception. const prevents you from pointing the variable at a new value. It does not freeze the contents of objects or arrays.

const user = { name: "Alice", age: 30 };
user.age = 31;         // Allowed — modifying the object's property
user = { name: "Bob" }; // TypeError — reassigning the variable itself

const colors = ["red", "green"];
colors.push("blue");   // Allowed — modifying the array
colors = [];           // TypeError — reassigning the variable itself

console.log(user);   // { name: "Alice", age: 31 }
console.log(colors); // ["red", "green", "blue"]

The variable user is a reference to an object in memory. const locks the reference, not the object it points to. If you need a truly immutable object, use Object.freeze().

When to Use Which

Follow this rule in professional code:

  1. Use const by default for everything.
  2. Switch to let only when you know the value will be reassigned (loop counters, accumulators, state that changes).
  3. Never use var in new code.
// Good
const API_URL = "https://api.example.com";
const maxRetries = 3;

let retryCount = 0;
while (retryCount < maxRetries) {
  // attempt request
  retryCount++;
}

Naming Conventions

Professional JavaScript uses these conventions consistently:

// camelCase for variables and functions
const userName = "alice";
const totalPrice = 49.99;
function getUserById(id) {}

// SCREAMING_SNAKE_CASE for true constants (module-level, never change)
const MAX_CONNECTIONS = 100;
const DEFAULT_TIMEOUT_MS = 5000;

// PascalCase for classes and constructor functions
class UserProfile {}
function Person(name) {}

// Prefix booleans with is, has, can, should
const isLoggedIn = true;
const hasPermission = false;
const canEdit = true;

Descriptive names eliminate the need for comments. userAge is better than a. filteredActiveUsers is better than result.

Real-World Scope Example

Consider a shopping cart that applies a discount only for members:

const MEMBER_DISCOUNT = 0.15;

function calculateTotal(items, isMember) {
  const subtotal = items.reduce((sum, item) => sum + item.price, 0);

  let discount = 0;
  if (isMember) {
    discount = subtotal * MEMBER_DISCOUNT;
  }

  const total = subtotal - discount;
  return total;
}

const cart = [
  { name: "Keyboard", price: 79 },
  { name: "Mouse", price: 29 },
];

console.log(calculateTotal(cart, true));  // 91.4
console.log(calculateTotal(cart, false)); // 108

Each variable has the smallest scope it needs. MEMBER_DISCOUNT is module-level. subtotal, discount, and total are local to the function. Nothing leaks, nothing is accidentally overwritten.

Key Takeaways

  • var is function-scoped and initializes to undefined when hoisted — avoid it.
  • let and const are block-scoped and throw a ReferenceError if accessed before initialization (TDZ).
  • const prevents reassignment, not mutation of objects and arrays.
  • Default to const. Use let when the value must change. Never use var.
  • Use camelCase for variables, SCREAMING_SNAKE_CASE for module constants, PascalCase for classes.
📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!