Skip to content

JavaScript Closure Variable Scope Fix

DodaTech Updated 2026-06-24 3 min read

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 let or const instead of var inside loops
  • Use IIFEs when you need to snapshot a value
  • Pass captured values as function parameters to make them explicit
  • Use Function.prototype.bind to preset arguments
  • Test closures with different async timings to verify correct values

Common Mistakes with closure scope

  1. Non-exhaustive pattern matches that compile with warnings then crash at runtime
  2. Misunderstanding that String is [Char] with poor performance for large text operations
  3. Using foldl instead of foldl' 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

### Why does my closure see the last value instead of each iteration's value?

Using var in a loop creates one scoped variable for the entire function. All closures reference the same variable. When the closure executes, it reads the current (final) value. Use let for block-scoped bindings.

Does arrow function syntax fix closure issues?

Arrow functions do not change how closure capture works. They only affect this binding. The same var/let rules apply whether you use function or =>.

How do I debug which value a closure captured?

Place a console.log inside the closure to inspect the value at execution time. Use browser DevTools Sources panel to set breakpoints inside closures and inspect the closure scope variables in the Scope pane.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro