
When writing JavaScript, one of the classic headaches developers run into is how functions behave inside loops. If you’ve ever expected a function in a loop to “remember” the value of a variable at a specific iteration, only to find it doesn’t, you’ve bumped into scope and binding issues.
Let’s break this down and see how block binding can solve it.
The Old Problem with var
in Loops
Traditionally, JavaScript used var
for variable declarations. The catch is that var
is function-scoped, not block-scoped. This means that when you use var
inside a loop, the variable is shared across all iterations.
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
You might expect this to log 0
, 1
, and 2
. But after one second, it prints:
3
3
3
Why? Because by the time the setTimeout
callback runs, the loop has already finished, and i
is 3
. Each callback references the same i
.
Enter Block Binding with let
The introduction of let
(and const
) in ES6 changed the game. Unlike var
, let
is block-scoped. That means each iteration of the loop creates a new binding of the variable.
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
This prints:
0
1
2
Exactly what you’d expect! Each setTimeout
captures a fresh copy of i
because let
binds the variable to the block of the current iteration.
Using IIFEs (Before let
)
Before ES6, developers often used Immediately Invoked Function Expressions (IIFEs) to work around this issue.
for (var i = 0; i < 3; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, 1000);
})(i);
}
By wrapping the loop variable inside a function call, you created a new scope that preserved the value of i
for each iteration. It worked, but it wasn’t pretty. let
makes this cleaner and more intuitive.
Functions in Loops – Best Practices
- Prefer
let
orconst
inside loops. It avoids scope confusion and leads to predictable behavior. - Use
const
when possible. If you don’t need to reassign,const
ensures the variable reference won’t change. - Understand closures. Even with
let
, functions inside loops still create closures. Knowing how they capture variables helps avoid surprises. - Keep it clean. Avoid deeply nesting functions in loops if you can restructure your code for clarity.
Why This Matters
Modern JavaScript relies heavily on asynchronous callbacks, promises, and event handlers. Understanding block binding is essential when your looped functions run later (e.g., API calls, animations, timers). Using the right variable declarations prevents subtle, hard-to-debug errors.
Quick Example with fetch
Here’s a real-world example. Suppose you’re fetching multiple URLs:
const urls = ["a.json", "b.json", "c.json"];
for (let i = 0; i < urls.length; i++) {
fetch(urls[i])
.then(response => response.json())
.then(data => {
console.log(`Result from ${urls[i]}:`, data);
});
}
Without block binding, you’d risk logging the wrong URL for the results. With let
, each iteration properly remembers its own value.
Final Thoughts
Block binding with let
(and const
) solved one of the most notorious quirks of JavaScript loops. It makes code more predictable, cleaner, and easier to maintain. If you’re still using var
out of habit, consider updating your loops—your future self will thank you.