Online Compiler logoOnline Compiler

JavaScript Tutorial

Var vs Let vs Const — Practical JavaScript Guide

This guide explains `var`, `let`, and `const` with clear rules, runnable examples, and best practices to write safer and more predictable JavaScript.

Why this matters

Choosing the correct declaration prevents bugs related to scope, hoisting, and accidental reassignment — essential for clean, maintainable code.

Quick summary: when to use which

`const` — use it by default for values that should not be reassigned. It provides clearer intent and reduces accidental bugs.

`let` — use when you need to reassign, such as loop counters or stateful variables.

`var` — function-scoped and legacy; avoid in modern code unless maintaining old codebases.

Hoisting and Temporal Dead Zone (TDZ)

Declarations are hoisted differently: `var` is hoisted and initialized with `undefined`, so accessing it before declaration returns `undefined` instead of throwing.

`let` and `const` are hoisted but not initialized. Accessing them before declaration throws a `ReferenceError` — this is called the temporal dead zone.

TDZ catches many accidental early accesses and is a helpful safety feature in modern JavaScript.

Block vs Function Scope

`let` and `const` are block-scoped — they live inside `{}` blocks (for, if, while, etc.). `var` is function-scoped and can escape block boundaries unexpectedly.

Prefer block-scoped declarations to avoid accidental globals and subtle loop bugs.

`const` doesn’t make objects immutable

`const` prevents reassignment of the binding, but object properties can still change.

Use `Object.freeze()` or immutable update patterns when you need deep immutability.

Best practices and migration

Start with `const` as the default and switch to `let` only when reassignment is necessary.

Avoid using `var` in new code; migrate legacy `var` to `let`/`const` as part of refactors.

Keep variable lifetimes small and prefer descriptive names to make intent obvious.

Code Examples

Block scope vs function scope

if (true) {
  var oldWay = "visible outside block";
  let modernWay = "only inside block";
}

console.log(oldWay); // works
// console.log(modernWay); // ReferenceError

`var` leaks outside block; `let` does not — use block scope for predictable variables.

Temporal Dead Zone example

function demo() {
  // console.log(x); // ReferenceError for let/const
  let x = 10;
  console.log(x);
}
demo();

Accessing `let`/`const` before declaration throws due to TDZ — helps avoid accidental early access.

const with object mutation

const config = { mode: "light" };
config.mode = "dark"; // allowed
console.log(config.mode);

// config = {}; // TypeError if uncommented

`const` locks the binding but not the internal object — mutate properties carefully.

Common Mistakes and Fixes

Using `var` in loops leads to closure bugs

Use `let` for loop variables so each iteration gets a fresh binding.

Expecting `const` to be immutable

Use `Object.freeze` or create new objects for immutable updates when needed.

Accessing `let`/`const` before declaration

Declare variables at the top of their block or avoid early access to prevent TDZ errors.

Frequently Asked Questions

Should I always use `const`?

Yes — prefer `const` by default and use `let` when you need to reassign. This makes code intent clearer and reduces bugs.

Can `const` values change?

The binding cannot be reassigned, but object or array contents can change. Use immutability helpers when you need immutable state.

Why avoid `var` in modern code?

`var` is function-scoped and can lead to unexpected behavior due to hoisting and leakage outside blocks. `let`/`const` are safer and clearer.

Related JavaScript Topics