var and let both declare variables, but their scope behavior is very different. var is function-scoped. let is block-scoped. That difference matters most in loops, closures, callbacks, and code that expects variables to disappear after an if or for block.
Modern JavaScript should usually prefer const first and let when reassignment is needed. Use var mainly when reading or maintaining older code.
Function scope with var
var is scoped to the nearest function, not the nearest block:
function example() {
if (true) {
var x = 10
}
console.log(x) // 10
}
example()
Even though x is declared inside the if block, it is available throughout the function.
Block scope with let
let is scoped to the nearest block:
function example() {
if (true) {
let x = 10
}
console.log(x) // ReferenceError
}
example()
The variable exists only inside the if block.
The classic loop closure problem
With var:
function createCounters() {
const counters = []
for (var i = 0; i < 3; i++) {
counters.push(function () {
return i
})
}
return counters
}
const counters = createCounters()
console.log(counters[0]()) // 3
console.log(counters[1]()) // 3
console.log(counters[2]()) // 3
All three functions share the same i. After the loop finishes, i is 3.
With let:
function createCounters() {
const counters = []
for (let i = 0; i < 3; i++) {
counters.push(function () {
return i
})
}
return counters
}
Now the functions return:
0
1
2
Each loop iteration gets its own binding.
Why IIFE used to appear
Before let was available, developers often used an immediately invoked function expression:
for (var i = 0; i < 3; i++) {
;(function (j) {
counters.push(function () {
return j
})
})(i)
}
The IIFE creates a new function scope and passes the current value as j. In modern code, let is clearer.
Hoisting note
Both var and let declarations are processed before execution, but they behave differently before the declaration line. var can be read as undefined; let is in the temporal dead zone and throws if accessed too early.
console.log(a) // undefined
var a = 1
console.log(b) // ReferenceError
let b = 1
This is another reason let and const catch mistakes earlier.
Related FreeMac guides
- For the loop-specific closure example, read Why var and let Behave Differently in for Loops.
- For array iteration choices, read JavaScript Array Methods: map, splice, sort, and for...of.
- For async order, read JavaScript Event Loop: Tasks, Microtasks, and Rendering.
Continue reading
Why var and let Behave Differently in for Loops
Use the classic JavaScript closure example to understand why var in a for loop prints 3 3 3, while let creates per-iteration bindings and prints 0 1 2.
8 min readTypeScript as vs satisfies: When to Use Each
Understand the difference between TypeScript as assertions and the satisfies operator: when to assert runtime knowledge, when to validate object structure, and how to preserve literal inference.
7 min readJavaScript Optional Chaining: When to Use `?.`
Learn how optional chaining works for property access, array indexes, and function calls, and when to pair it with nullish coalescing instead of hiding real data errors.
Subscribe to FreeMac
Weekly picks: free Mac software reviews, trusted source updates, alternatives, and low-friction guides.