πŸŒ€ Demystifying var, let, and const in Loops

pushpesh kumarpushpesh kumar
4 min read

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

KeywordScopeRe-assignableRe-declarableCreates new binding per iteration?
varFunctionβœ… Yesβœ… Yes❌ No
letBlockβœ… Yes❌ Noβœ… Yes
constBlock❌ 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 the i++ 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 because const 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?

Featurevarletconst
ScopeFunction/globalBlock (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 and const create new bindings each iteration β€” but const can’t be updated, so it fails in classic for loops with i++.


✨ 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 in for...of, fails with i++.

0
Subscribe to my newsletter

Read articles from pushpesh kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

pushpesh kumar
pushpesh kumar