JUNE 2026 Coding interviews test recall. kodr.run tests engineering judgment
JavaScriptIntermediate19 minGeneral

JavaScript Closures & Scope Interview Questions (With Code Examples)

Why interviewers ask this

Closures are one of the few JavaScript concepts that are simple to define but genuinely hard to apply correctly under pressure. Interviewers lean on that gap: a candidate who can recite "a closure is a function with access to its outer scope" but freezes when asked to build a private counter or fix a scoping bug hasn't actually internalized the concept. This guide is built around the applied side.

The first four questions get full treatment: working code, the naive or buggy alternative, and the explanation. The remaining six are tighter: one working example and the insight it tests.

How interviewers read a closures/scope answer

LayerWhat a weaker answer doesWhat a stronger answer does
ApplicationCan define a closure but can't build one to solve an actual problem (private state, caching)Reaches for a closure naturally when the problem calls for persistent, encapsulated state
Scope reasoningGuesses at variable lookup behaviorCan trace the scope chain step by step to explain why a variable resolves the way it does
Bug recognitionDoesn't notice when a loop variable is shared incorrectly across closuresImmediately spots var in a loop with async callbacks as a scope bug, and knows two different fixes
CommunicationPresents working code without explaining why it worksNarrates which scope each variable belongs to and why

1How do you use closures to create private variables?

JavaScript · private-variables-module-pattern.js
function createBankAccount(initialBalance) {
    let balance = initialBalance;


    return {
        deposit(amount) { balance += amount; return balance; },
        withdraw(amount) { balance -= amount; return balance; },
        getBalance() { return balance; }
    };
}


const account = createBankAccount(100);
console.log(account.deposit(50)); // 150
console.log(account.withdraw(30)); // 120
console.log(account.balance); // undefined, not directly accessible

balance is only reachable through the three returned methods, since it lives in createBankAccount's scope and each method forms a closure over it. There's no way to reach in and set balance directly from outside, which is the entire point.

Compare this to the naive version, a plain object with a public property:


JavaScript · public-property-no-privacy.js
// No real privacy: balance can be changed directly, bypassing any logic
const account2 = { balance: 100 };
account2.balance = -99999; // nothing stops this

Evaluates: whether the candidate can use a closure to enforce actual encapsulation, not just describe what encapsulation means. This is the module pattern, one of the most common practical uses of closures in real code, predating JavaScript's class and #privateField syntax.

2How do you memoize an expensive function using a closure?

JavaScript · memoize-with-closure.js
function memoize(fn) {
    const cache = new Map();
    return function (...args) {
        const key = JSON.stringify(args);
        if (cache.has(key)) {
            return cache.get(key);
        }
        const result = fn(...args);
        cache.set(key, result);
        return result;
    };
}


function slowSquare(n) {
    for (let i = 0; i < 1e8; i++) {} // simulate expensive work
    return n * n;
}


const fastSquare = memoize(slowSquare);
console.log(fastSquare(5)); // slow the first time
console.log(fastSquare(5)); // instant, served from cache

The cache variable persists across every call to the returned function because it's captured in the closure, exactly like the private balance in Q1. Without memoization, every call to slowSquare repeats the same expensive work, even for a repeated input:


JavaScript · no-memoization-repeated-work.js
// No caching: repeats the expensive work every single time
console.log(slowSquare(5)); // slow
console.log(slowSquare(5)); // slow again, same input

Evaluates: whether the candidate recognizes memoization as another closure application, using persistent state to avoid redundant work, rather than treating it as an unrelated caching trick.

3How do you fix the classic "loop variable captured incorrectly" bug without using let?

JavaScript · loop-var-iife-fix.js
// The bug: every callback shares the same i, which ends at 3
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3

Our anchor guide covers the modern fix, switching var to let. But interviewers sometimes ask for the fix without let, to check whether a candidate understands why the bug happens, not just which keyword resolves it:


JavaScript · loop-var-bug-recap.js
// Fix using an IIFE (immediately invoked function expression)
for (var i = 0; i < 3; i++) {
    (function (capturedI) {
        setTimeout(() => console.log(capturedI), 100);
    })(i);
}
// Output: 0, 1, 2

The IIFE creates a new function scope on every iteration, and capturedI is a fresh parameter each time, so each setTimeout closes over its own copy instead of sharing the loop's single i.

Evaluates: understanding that the fix isn't about the specific keyword, it's about creating a new scope per iteration. let's per-iteration binding and the IIFE pattern solve the exact same underlying problem two different ways, and knowing both signals real understanding rather than a memorized fix.

4What is the scope chain, and how does JavaScript resolve a variable lookup?

JavaScript · scope-chain-nested-functions.js
const globalVar = "global";


function outer() {
    const outerVar = "outer";


    function inner() {
        const innerVar = "inner";
        console.log(innerVar, outerVar, globalVar);
    }


    inner();
}


outer(); // inner outer global

When inner references outerVar, JavaScript doesn't find it in inner's own scope, so it looks outward to outer's scope, where it finds it. If it weren't there either, it would keep looking outward to the global scope. This outward chain of enclosing scopes is the scope chain, and it's fixed at the point the function is defined, not where it's called.


JavaScript · lexical-scope-vs-call-site.js
// Scope is determined by where a function is written, not where it's called
function makeGreeter() {
    const greeting = "hello";
    return function () { console.log(greeting); };
}


const greet = makeGreeter();
function callElsewhere(fn) {
    const greeting = "goodbye"; // this greeting is irrelevant to fn
    fn();
}
callElsewhere(greet); // hello, not goodbye

Evaluates: whether the candidate understands lexical scoping specifically, that scope is determined by where code is physically written, not by the call stack at runtime. This is the mental model that makes every other closure question in this guide make sense.

5How do you implement a curry function using closures?

JavaScript · curry-function.js
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn(...args);
        }
        return (...moreArgs) => curried(...args, ...moreArgs);
    };
}


function add3(a, b, c) { return a + b + c; }
const curriedAdd = curry(add3);


console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6

Evaluates: whether the candidate can trace how each returned function closes over the arguments accumulated so far, which is what lets partial application work regardless of how the arguments are split across calls.

6How do you build a function factory, like a generator for multiplier functions?

JavaScript · function-factory-multiplier.js
function makeMultiplier(factor) {
    return function (n) {
        return n * factor;
    };
}


const double = makeMultiplier(2);
const triple = makeMultiplier(3);


console.log(double(5)); // 10
console.log(triple(5)); // 15

Evaluates: recognizing that each call to makeMultiplier creates a distinct closure over its own factor, so double and triple don't interfere with each other despite being built from the same function.

7What's the difference between global scope, function scope, and block scope?

JavaScript · global-function-block-scope.js
const globalVar = "I'm global"; // global scope


function example() {
    var functionScoped = "I'm function-scoped"; // visible anywhere in example()
    if (true) {
        let blockScoped = "I'm block-scoped"; // only visible inside this if block
        console.log(blockScoped);
    }
    // console.log(blockScoped) here would throw a ReferenceError
}

Evaluates: knowing var respects only function boundaries (it "leaks" out of blocks like if and for), while let and const respect block boundaries, which is the direct cause of the classic loop-variable bug in Q3.

8What's a common closures bug with event handlers created inside a loop?

JavaScript · event-handler-loop-closure-bug.js
// Bug: every button's click handler logs 3, the final value of i
const buttons = document.querySelectorAll("button");
for (var i = 0; i < buttons.length; i++) {
    buttons[i].addEventListener("click", () => console.log(`Button ${i} clicked`));
}

Evaluates: recognizing this as the exact same underlying bug as Q3, var's shared binding across loop iterations, just in a DOM-event context instead of setTimeout. Candidates who solved Q3 should immediately recognize the fix here is identical: switch to let.

9What's the temporal dead zone, and how does it relate to let and const?

JavaScript · temporal-dead-zone.js
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 5;


console.log(b); // undefined (no error)
var b = 5;

Evaluates: knowing that let and const are hoisted to the top of their scope like var, but remain in an unusable "temporal dead zone" until their declaration line actually executes, which is why accessing them early throws an error instead of silently returning undefined.

10How do you implement a simple throttle function using closures?

JavaScript · throttle-function.js
function throttle(fn, limit) {
    let waiting = false;
    return function (...args) {
        if (!waiting) {
            fn.apply(this, args);
            waiting = true;
            setTimeout(() => { waiting = false; }, limit);
        }
    };
}


const logScroll = throttle(() => console.log("scroll event"), 1000);

Evaluates: distinguishing throttle from debounce (covered in the anchor guide): throttle guarantees execution at a steady rate during continuous activity, while debounce waits for activity to stop entirely. Both rely on a closure holding a flag or timer across calls, but they solve different problems.

How should you prepare for a closures and scope interview?

  1. Practise building with closures, not just defining them. Private state, memoization, and currying are where interviewers actually test understanding. Being able to recite a textbook definition isn't the same skill.
  2. Trace the scope chain by hand. For any nested function, be able to say out loud which scope each variable comes from, and why, before you need to debug a real scoping bug under pressure.
  3. Know the var-in-a-loop bug cold, in every form it appears. setTimeout, event handlers, and array callbacks are all the same underlying issue wearing different clothes.

Frequently asked questions

Who is this guide for, and what JavaScript experience does it assume? This guide assumes you already understand what a closure is, covered in our anchor guide, and want to go deeper into applied closure patterns and scope mechanics.

Is the module pattern still relevant now that JavaScript has classes and private fields? Yes, in two ways: it's still used in real code, particularly in library and module design, and interviewers ask about it specifically because building privacy with closures demonstrates a deeper understanding than just using the #privateField syntax.

What's the most commonly missed question in this category? The temporal dead zone (Q9). Most candidates know let and const are "block-scoped" but don't realize they're still hoisted, just unusable before their declaration, which trips people up specifically because the error message doesn't match their mental model of hoisting.

Is throttle or debounce asked more often? Both come up regularly, usually together as a comparison question, since they solve similar-sounding but functionally different problems and both rely on the same closure mechanism.

How is this different from the general JavaScript interview guide? The Top JavaScript Coding Interview Questions guide introduces closures alongside many other topics. This guide goes deeper specifically into closures, scope, and the patterns built on top of them.



Related tutorials: Top JavaScript Coding Interview Questions · JavaScript Array Methods Interview Questions · JavaScript Async & Promises Interview Questions