Last modified: Jul 22, 2026
Var vs Let in Loops Pitfalls
JavaScript variables in loops can cause confusing bugs. The choice between var and let changes how your code behaves. Many developers face unexpected results when using var inside loops. This article explains the differences clearly.
Understanding scope is the key to avoiding these pitfalls. var has function scope. let has block scope. This simple difference creates major issues in loops.
The Classic Loop Problem
Consider this common example. You want to create buttons that show their number when clicked. Using var gives unexpected results.
// Example with var - wrong behavior
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // Outputs: 3, 3, 3
}, 100);
}
3
3
3
Why does this happen? The var variable i is hoisted to the function scope. After the loop finishes, i equals 3. All callbacks reference the same i variable. They all see 3.
This is a closure problem. Each callback closes over the same i variable. The loop doesn't create a new scope for each iteration.
How Let Solves This
Using let creates a new binding for each loop iteration. Each callback gets its own copy of i.
// Example with let - correct behavior
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // Outputs: 0, 1, 2
}, 100);
}
0
1
2
Each iteration creates a new block scope. The let variable is scoped to that block. Each callback captures its own i value. This is the expected behavior.
For more on variable scope, see our JavaScript Variable Scope Explained guide.
Event Handlers in Loops
Another common pitfall is adding event listeners inside loops. Using var causes all handlers to use the last value.
// Wrong with var
var buttons = document.querySelectorAll('button');
for (var j = 0; j < buttons.length; j++) {
buttons[j].addEventListener('click', function() {
console.log('Button ' + j + ' clicked');
// Always shows: Button 3 clicked
});
}
All buttons show "Button 3" because j equals buttons.length after the loop. The fix is simple: use let.
// Correct with let
for (let j = 0; j < buttons.length; j++) {
buttons[j].addEventListener('click', function() {
console.log('Button ' + j + ' clicked');
// Shows correct button index
});
}
Variable Shadowing in Loops
Using var can also cause variable shadowing issues. If you declare a variable with the same name inside the loop, it shadows the outer variable.
// Shadowing with var
var x = 10;
for (var x = 0; x < 3; x++) {
console.log(x); // 0, 1, 2
}
console.log(x); // 3 - outer variable is overwritten!
0
1
2
3
The var declaration inside the loop overwrites the outer x. This is a common source of bugs. Using let prevents this problem.
Learn more about this in our JavaScript Variable Shadowing article.
Async Operations and Var
Async code inside loops amplifies the var problem. Promises, callbacks, and async/await all suffer from the same issue.
// Async with var - buggy
for (var k = 0; k < 5; k++) {
fetch('/api/data/' + k)
.then(function() {
console.log('Request ' + k + ' completed');
// All show: Request 5 completed
});
}
All fetch callbacks see k = 5. The loop finishes before any response arrives. Use let to capture each value correctly.
// Async with let - correct
for (let k = 0; k < 5; k++) {
fetch('/api/data/' + k)
.then(function() {
console.log('Request ' + k + ' completed');
// Shows correct request number
});
}
Best Practices for Loop Variables
Follow these rules to avoid pitfalls. Always use let for loop counters unless you have a specific reason for var.
Use const when you don't reassign the variable. For example, when iterating over arrays with for...of.
// Using const with for...of
const items = [1, 2, 3];
for (const item of items) {
console.log(item); // 1, 2, 3
}
Avoid using var in modern JavaScript. It creates confusing scope behavior. Use let and const for clear, predictable code.
For more on variable types, check our Types of JavaScript Variables guide.
Common Mistakes to Avoid
Here are frequent errors with loop variables:
- Using
varin async callbacks inside loops - Sharing loop variables between nested functions
- Relying on closure behavior without understanding scope
- Forgetting that
varhoists to the function scope
Test your code with different loop sizes. The bug often appears only with multiple iterations. Use console logs to verify variable values at each step.
Conclusion
Understanding var vs let in loops is essential for JavaScript developers. The function scope of var creates subtle bugs with closures and async code. The block scope of let gives predictable, per-iteration bindings.
Always prefer let for loop counters. This simple choice prevents hours of debugging. Modern JavaScript encourages let and const over var for cleaner, safer code.
By mastering this concept, you write more reliable code. Your loops behave as expected. Your event handlers work correctly. Your async operations capture the right values.
Remember: scope matters. Choose the right variable keyword for your loops. Your future self will thank you.