JavaScript Closure Variable Scope Fix
In this tutorial, you'll learn about JavaScript Closure Variable Scope Fix. We cover key concepts, practical examples, and best practices.
The Problem
Closures in JavaScript capture variables by reference, not by value. When a closure runs later — inside a setTimeout, event handler, or async callback — the captured variable may hold a different value than expected because the surrounding scope changed.
Quick Fix
Step 1: Capture loop variables with let
A classic closure bug happens inside loops:
// Wrong — var creates one shared binding
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3
}, 100);
}
// Right — let creates a new binding each iteration
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 100);
}
Step 2: Create a fresh scope with an IIFE
When you must use var or need an explicit snapshot:
// Wrong — all callbacks reference the same i
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3
}, i * 100);
}
// Right — IIFE captures the current value
for (var i = 0; i < 3; i++) {
(function(index) {
setTimeout(function() {
console.log(index); // 0, 1, 2
}, index * 100);
})(i);
}
Step 3: Use closures in event handlers
function createButtons() {
// Wrong — all handlers use the same msg
for (var i = 0; i < 3; i++) {
var msg = 'Button ' + i;
var btn = document.createElement('button');
btn.textContent = msg;
btn.onclick = function() {
console.log(msg); // always "Button 3"
};
document.body.appendChild(btn);
}
}
function createButtons() {
// Right — each click uses its own msg
for (let i = 0; i < 3; i++) {
let msg = 'Button ' + i;
var btn = document.createElement('button');
btn.textContent = msg;
btn.onclick = function() {
console.log(msg); // Button 0, Button 1, Button 2
};
document.body.appendChild(btn);
}
}
Step 4: Return a function from a function
function makeMultiplier(x) {
// Wrong — returns undefined, missing return
function multiply(y) {
return x * y;
}
}
// Right — return the inner function
function makeMultiplier(x) {
return function(y) {
return x * y;
};
}
const double = makeMultiplier(2);
console.log(double(5)); // 10
Prevention
- Use
letorconstinstead ofvarinside loops - Use IIFEs when you need to snapshot a value
- Pass captured values as function parameters to make them explicit
- Use
Function.prototype.bindto preset arguments - Test closures with different async timings to verify correct values
Common Mistakes with closure scope
- Non-exhaustive pattern matches that compile with warnings then crash at runtime
- Misunderstanding that
Stringis[Char]with poor performance for large text operations - Using
foldlinstead offoldl'causing stack overflow on large lists
These mistakes appear frequently in real-world JS code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.
Practice Exercise
Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.
This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro