JavaScript Scope & Closure: Stop Memorizing, Start Understanding

Published: (December 26, 2025 at 05:56 AM EST)
6 min read
Source: Dev.to

Source: Dev.to

Let’s Be Brutally Honest

When you walk into a JavaScript technical interview, the interviewer isn’t looking for someone who copy‑paste from Stack Overflow. They want someone who understands how the machine works.

Two concepts trip up developers more than any others: Scope and Closure.

Most developers have a vague, “hand‑wavy” understanding of them. They know that it works, but not why. In an interview, “hand‑wavy” doesn’t cut it. When you can’t explain why a variable is undefined, or why a loop prints the wrong number, you fail the test.

To master these concepts, stop treating JavaScript like a black box and start thinking like the JavaScript engine.

Part 1 – Scope Is a Set of Rules

The first mistake developers make is thinking scope is a physical “place” where variables live.

It’s better to think of scope as a set of rules that the JavaScript engine uses to figure out where to look for a variable by its identifier name.

JavaScript uses something called lexical scope. This is the most important term you will read today. “Lexical” relates to the lexing or parsing stage of compilation.

In plain English: Scope is determined by where you, the author, physically write the code. The nesting of your functions determines the nesting of your scopes. Once written, these rules are (mostly) set in stone before the code even runs.

The Mental Model: The Office Building

Imagine your program is a multi‑story office building.

LevelDescription
Ground floor (lobby)Global scope
Each function you declareA new, private floor above the one you are currently on

When the engine is executing code inside a function (say, on the 3rd floor) and it needs to find a variable (let’s call it starthroat), it follows a strict procedure:

  1. Look locally – Does the 3rd floor have its own starthroat? If yes, use it.
  2. Look outward – If not, take the stairs down to the 2nd floor. Do they have it?
  3. Repeat – Keep going down one floor at a time until you reach the lobby (global scope).
  4. Give up – If it’s not in the lobby, throw a ReferenceError. It doesn’t exist.

Crucial interview note: Scope lookups only go up (outward). They never go down (inward). The lobby cannot see what’s happening on the 3rd floor.

var buildingName = "JS Towers"; // Lobby (global)

function floorTwo() {
    var manager = "Kyle"; // 2nd‑floor scope

    function floorThree() {
        var developer = "You"; // 3rd‑floor scope

        // Engine looks here (floor 3), finds nothing.
        // Goes down to floor 2, finds `manager`. Success.
        console.log(manager);
    }
}

Part 2 – Closure Is Not Magic

If scope is the set of rules for lookup, closure is what happens when you bend those rules.

Many define closure vaguely. Let’s define it precisely:

Closure is observed when a function executes outside of its lexical scope, but still maintains access to that scope.

Normally, when a function finishes executing, its scope is garbage‑collected and the memory is freed. Closure prevents that from happening.

The Mental Model: The Backpack

If you define Function B inside Function A, Function B gets a “hidden link” to Function A’s surrounding scope.

When Function B is passed out of Function A to be used elsewhere, it doesn’t leave empty‑handed. It takes that hidden link with it.

Think of it like a backpack. Function B carries a backpack containing all the variables that existed in Function A when Function B was created. Whenever Function B runs—no matter where it is, or how much later in time—it can open that backpack and access those variables.

function outer() {
    var secret = "XYZ_123"; // By the rules of scope, this should die when outer() finishes.

    function inner() {
        // `inner` closes over the `secret` variable.
        // It puts `secret` in its backpack.
        console.log("The secret is: " + secret);
    }

    return inner; // We send `inner` out into the world.
}

// outer() runs and finishes entirely.
var myRef = outer(); // `inner` is now stored in `myRef`

// ... hours later ...

// `myRef` is executed in the global scope.
// Yet, it still remembers the scope of `outer`.
myRef(); // "The secret is: XYZ_123"

In an interview, don’t just say “it remembers.” Say:

“Because of closure, inner retains a reference to the lexical scope of outer, preventing that scope from being garbage‑collected.”

Part 3 – The Classic Interview Trap

If you are asked about closure in an interview, there is a ~90 % chance you’ll see a variation of the following loop problem. It tests whether you understand the difference between scope boundaries and value references.

The Problem

for (var i = 1; i  // (original code omitted in source)

Pro tip: To fix the problem, either create a new lexical environment per iteration (using let instead of var) or capture the current value with an IIFE or bind.

Using let (block‑scoped)

// Using let (block‑scoped)
for (let i = 1; i  console.log(i), i * 1000);
}

Using an IIFE

// Using an IIFE
for (var i = 1; i  console.log(j), j * 1000);
    })(i);
}

Bottom Line

  • Scope = the rules the engine follows to locate identifiers.
  • Closure = the mechanism that lets a function retain access to those rules after its original lexical environment would normally be gone.

Master these mental models, and you’ll be able to answer any interview question about scope or closure with confidence—and, more importantly, you’ll understand why JavaScript behaves the way it does.

The Fix (Pre‑ES6 IIFE Pattern)

You need to create a new scope for every iteration of the loop to “capture” the current value of i.

for (var i = 1; i <= 3; i++) {
    // Create an Immediately Invoked Function Expression (IIFE)
    // This creates a new scope bubble for every loop iteration.
    (function (j) { 
        setTimeout(function timer() {
            // timer now closes over 'j', which is unique to this iteration's scope
            console.log(j); 
        }, j * 1000);
    })(i); // Pass in current 'i' value
}

(Note: In modern JS, you would just change var i to let i in the for‑loop head, because let creates a new block scope for every iteration automatically. Mentioning both shows historical context and modern knowledge.)

Summary

Don’t memorize code snippets. Memorize the models.

  • Scope is about where variables are accessible, determined at write‑time (lexically). Think of the office building floors.
  • Closure is about when variables are accessible. A function remembers its lexical scope even when executed later. Think of the backpack.

When you look at code in an interview, trace the scope lines mentally. Ask yourself:

  1. “Which scope bucket does this variable belong to?”
  2. “What is this function carrying in its backpack?”

Do that, and you won’t just pass the interview—you’ll actually understand the language you use every day.

Back to Blog

Related posts

Read more »

VAR, LET, & CONST in JavaScript

🧠 How JavaScript Runs Your Code Very Important JavaScript doesn’t execute your code line‑by‑line immediately. It first prepares everything, then runs it. Memo...

Objects in JavaScript

What is Objects? - An object is a variable that can hold multiple variables. - It is a collection of key‑value pairs, where each key has a value. - Combination...