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 var in async callbacks inside loops
  • Sharing loop variables between nested functions
  • Relying on closure behavior without understanding scope
  • Forgetting that var hoists 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.