π Demystifying var, let, and const in Loops

When working with JavaScript, one of the most confusing topics is how var
, let
, and const
behave inside loops. Understanding this deeply will help you avoid weird bugs (especially with asynchronous code).
π± Quick Recap
Keyword | Scope | Re-assignable | Re-declarable | Creates new binding per iteration? |
var | Function | β Yes | β Yes | β No |
let | Block | β Yes | β No | β Yes |
const | Block | β No | β No | β Yes (but cannot update) |
π’ var
in loops
Scope
var
is function-scoped, meaning it is not bound to block {}
.
for (var i = 0; i < 3; i++) {
console.log(i);
}
console.log("Outside:", i); // β
Prints 3
Same variable reused
When using var
, there is only one single i
variable created before the loop starts.
Each iteration updates the same
i
.No new binding is created.
Closure issue
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 100);
}
β Output:
3
3
3
Why?
All callbacks refer to the same shared
i
.After loop finishes,
i
is 3. So when callbacks run later, they print 3.
π let
in loops
Scope
let
is block-scoped, so the variable exists only inside each loop block.
for (let i = 0; i < 3; i++) {
console.log(i);
}
console.log("Outside:", i); // β ReferenceError
New binding per iteration
For each iteration:
JavaScript creates a new
i
in a fresh scope.In each iteration, JavaScript creates a new
i
binding and assigns it the result of thei++
update expression from the previous iteration.
Works perfectly with closures
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 100);
}
β Output:
0
1
2
Why?
- Each iterationβs callback "closes over" its own separate
i
.
π΄ const
in loops
Common confusion
for (const i = 0; i < 3; i++) {
console.log(i);
}
β Error: Assignment to constant variable
Why does it fail?
In classic for
loops, you have:
for (initialization; condition; update) { ... }
const i = 0
β initialization β works.Condition β fine.
Update (
i++
) β β fails becauseconst
cannot be re-assigned.For each iteration a new const is created in a new scope . For the first time in the first scope, a const is created and JS engine tries to update the value before creating a new scope. Since updation with const is not allowed so it fails.
Creates new binding per iteration?
β
Yes! Like let
, const
does create a new binding per iteration, but since it cannot be updated, it canβt work with i++
.
Works in for...of
const arr = [10, 20, 30];
for (const item of arr) {
console.log(item);
}
β Output:
10
20
30
No update step (
i++
).New
const
binding created each iteration.No re-assignment needed.
βοΈ Detailed comparison: What happens per iteration?
Feature | var | let | const |
Scope | Function/global | Block (new each iteration) | Block (new each iteration) |
Variable creation per iteration | β Only one variable reused | β New variable created | β New variable created |
Update possible? | β Yes | β Yes | β No |
Works with i++ ? | β Yes | β Yes | β No |
Commonly causes closure issues? | β Yes | β No | β No |
π Practical tips
β
Use let
in loops when you want separate values and safe closure behavior.
β
Use const
in for...of
or for...in
loops where no update is needed.
β Avoid var
in new code unless absolutely necessary (e.g., for old code compatibility).
π¬ One-liner summary
var
reuses one variable across all iterations,let
andconst
create new bindings each iteration β butconst
canβt be updated, so it fails in classicfor
loops withi++
.
β¨ Final takeaways
var
: single shared variable, leads to unexpected bugs in closures.let
: new variable each iteration, works perfectly with asynchronous callbacks.const
: new variable each iteration, but cannot be updated β works great infor...of
, fails withi++
.
Subscribe to my newsletter
Read articles from pushpesh kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
