In many circumstances, let and var can seem to be used interchangeably. This, however, is not the case.
Table of Contents
Here’s some great write-ups on stack overflow about the matter:
https://stackoverflow.com/questions/762011/whats-the-difference-between-using-let-and-var
The Short Answer
In modern JavaScript (ES6 and later), always use let or const. There is virtually no reason to use var in new code. The var keyword has quirky behavior that leads to bugs, and let/const were specifically designed to fix those problems.
Use const when the variable won’t be reassigned (most variables). Use let when you need to reassign the value (loop counters, accumulators, state that changes). Use var… never, unless you’re maintaining legacy code.
The Key Difference: Scope
The most important difference between var and let is scope:
varis function-scoped: it’s accessible anywhere within the function it’s declared inletis block-scoped: it’s only accessible within the nearest enclosing block ({})
Here’s a practical example that shows why this matters:
// var leaks out of blocks
if (true) {
var x = 10;
}
console.log(x); // 10 — var leaked out of the if block!
// let stays in its block
if (true) {
let y = 10;
}
console.log(y); // ReferenceError — y is not defined
The var version creates a variable that exists outside the if block, which is almost never what you intend. This behavior is a common source of subtle bugs, especially in loops.
The Classic Loop Bug
This is the most famous var gotcha and a staple of JavaScript interviews:
// Bug with var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3 (not 0, 1, 2!)
// Fixed with let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2 (correct!)
With var, there’s only one i variable shared across all iterations. By the time the setTimeout callbacks execute, the loop has finished and i is 3. With let, each iteration gets its own copy of i. The MDN documentation on let explains this scoping behavior in detail.
Hoisting: Another Key Difference
Both var and let are “hoisted” (the JavaScript engine knows about them before your code runs), but they behave differently:
// var is hoisted and initialized to undefined
console.log(a); // undefined (no error)
var a = 5;
// let is hoisted but NOT initialized (Temporal Dead Zone)
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 5;
The var behavior silently gives you undefined instead of throwing an error, which can hide bugs. The let behavior (called the Temporal Dead Zone) is much safer — it immediately tells you when you’re trying to use a variable before it’s declared.
What About const?
const works like let (block-scoped, not hoisted to undefined) but cannot be reassigned after initialization:
const PI = 3.14159;
PI = 3; // TypeError: Assignment to constant variable
// But objects/arrays can still be mutated!
const user = { name: 'Alice' };
user.name = 'Bob'; // This works! const prevents reassignment, not mutation
A common misconception is that const makes values immutable. It doesn’t — it only prevents reassignment of the variable itself. Object properties and array elements can still be modified.
Best Practices Summary
- Default to
const: Use it for any variable you won’t reassign (imports, configuration, computed values) - Use
letfor mutation: Loop counters, accumulators, values that change over time - Avoid
varentirely: It exists only for backward compatibility with pre-ES6 code - Declare at the top of scope: Even though
let/constare block-scoped, declaring them at the top of their scope improves readability
For more JavaScript fundamentals and web development tutorials, check out the GTWebs blog where we break down programming concepts for practical use.