Back to Blogs
Deep technical guide
JavaScript
Closures
Lexical Scope
Interview Questions
Frontend
Node.js

JavaScript Closures Explained: Scope, Lexical Environment, Loops, and Interview Questions

A detailed JavaScript closures guide for developers and interview preparation: lexical scope, private state, callbacks, loops, memory, React patterns, common mistakes, and answer-ready examples.

JS Interview Prep Editorial Team

Author

September 1, 2026

Published

6 min read

Reading time

1 views

Views

JavaScript Closures Explained: Scope, Lexical Environment, Loops, and Interview Questions
SEO-friendly JavaScript learning article

JavaScript Closures Explained: Scope, Lexical Environment, Loops, and Interview Questions

JavaScript closures are one of the most searched and most frequently misunderstood interview topics. They appear in callbacks, event listeners, module patterns, debouncing, currying, memoization, React hooks, and API clients. The definition is short, but a useful explanation has to connect scope to real code.

A closure is created when a function keeps access to variables from its lexical scope even after the outer function has returned. In practical terms, an inner function can remember the variables that were in scope where the function was created. This guide turns that sentence into code you can explain confidently in an interview and use safely in production.

What a closure is, in plain English

Lexical scope means JavaScript decides what variables a function can access based on where that function is written, not where it is called. When the function is later executed, it still has access to that original surrounding scope. That preserved access is a closure.

function createGreeting(name) {
  const greeting = 'Hello';

  return function greet() {
    return greeting + ', ' + name + '!';
  };
}

const greetAarav = createGreeting('Aarav');
console.log(greetAarav()); // Hello, Aarav!

createGreeting has finished before greetAarav is called, but greet still reads greeting and name. JavaScript retains the lexical environment needed by the returned function. The function does not copy those variables; it keeps access to the relevant bindings.

Closure versus scope versus lexical environment

  • Scope is the region where a variable can be accessed.
  • Lexical scope means the source-code location determines that access.
  • Lexical environment is the runtime record of bindings available to a scope.
  • Closure is a function together with its access to the lexical environment in which it was created.

Interviewers often ask this distinction because it shows whether you understand the mechanism rather than only repeating a definition. A concise answer is: scope controls visibility, while a closure is what lets a function keep using its lexical scope after execution moves elsewhere.

Private state with closures

Before private class fields became widely used, closures were the standard way to hide implementation state. They still work well for small factories where you want a narrow public API and no external mutation path.

function createCounter(start = 0) {
  let count = start;

  return {
    increment() {
      count += 1;
      return count;
    },
    current() {
      return count;
    },
  };
}

const counter = createCounter(10);
counter.increment(); // 11
counter.current();   // 11
// counter.count is undefined

Each call creates a separate count binding. This is important: closures do not create shared state unless multiple functions close over the same binding from the same factory call.

The classic var loop problem and the let fix

A closure captures the variable binding, not a frozen snapshot of its value. With var, a loop has one function-scoped i binding. By the time the timer callbacks run, the loop has already incremented i to 3.

for (var i = 0; i < 3; i += 1) {
  setTimeout(() => console.log(i), 0);
}
// 3, 3, 3

let creates a new block-scoped binding for each iteration, so each callback closes over the value for that iteration.

for (let i = 0; i < 3; i += 1) {
  setTimeout(() => console.log(i), 0);
}
// 0, 1, 2

If you must support an older var-based pattern, pass the value to an immediately invoked function expression. In modern JavaScript, prefer let for loop counters.

Closures in callbacks and event listeners

Closures make callbacks useful because a callback can retain context without global variables. A request handler can retain a request id, an event listener can retain a selected item id, and a retry function can retain attempt state.

function attachSaveHandler(button, documentId) {
  button.addEventListener('click', async () => {
    await saveDocument(documentId);
    console.log('Saved document:', documentId);
  });
}

The callback uses documentId later, after attachSaveHandler has returned. This is a normal and valuable closure. The production concern is cleanup: remove listeners when their associated UI is destroyed if the listener can otherwise keep large objects alive.

Closures and asynchronous JavaScript

Promises and async functions do not break closures. A function can retain variables across an await because the function resumes with access to its lexical environment. What changes is scheduling, not scope.

function createUserLoader(userId) {
  return async function loadUser() {
    const response = await fetch('/api/users/' + userId);
    return response.json();
  };
}

For timing details, review the JavaScript event loop guide on JS Interview Prep. Closures explain what data the callback can access; the event loop explains when the callback runs.

Closures in React and stale values

React function components render with a particular set of props and state. Event handlers and effects created during that render close over those values. This is why a callback can accidentally read stale state when dependencies or update patterns are incorrect.

function SearchBox() {
  const [query, setQuery] = useState('');

  function scheduleSearch() {
    setTimeout(() => {
      console.log(query); // query from this render
    }, 300);
  }
}

The right fix depends on the intention: use functional state updates when calculating from previous state, include required dependencies in effects, cancel outdated requests, or store intentionally mutable latest values in a ref. Do not treat closures as a React bug; they are the language behavior React builds on.

Memory and performance: when closures can retain too much

Closures are not automatically memory leaks. They become a problem when a long-lived function retains an object that is no longer useful. Common examples are unremoved listeners, timers that never clear, caches with no eviction policy, and closures that retain a large DOM subtree or response payload.

  • Remove event listeners during component or page cleanup.
  • Clear intervals and cancel pending work when it is no longer needed.
  • Keep callback captures small; pass an id instead of retaining a large object when practical.
  • Use bounded caches and explicit invalidation policies.
  • Profile before optimizing: a closure alone is not evidence of a leak.

Interview-ready answer and follow-up questions

A strong answer: “A closure is a function that retains access to variables from the lexical scope where it was created, even when it runs outside that scope. I use closures for private state, callbacks, factory functions, and event handlers. The important detail is that a closure captures bindings, so var in a loop shares one binding while let creates one per iteration.”

  • What does a closure capture? Variable bindings from the lexical environment, not a one-time value snapshot.
  • Can closures cause memory leaks? Yes, if a long-lived callback keeps otherwise-unused data reachable; the closure itself is not inherently a leak.
  • How do closures differ from classes? Closures can provide private state through factories; classes organize behavior around instances and can use private fields. Choose the clearest model for the problem.

Frequently asked questions

Are closures created only when a function is returned?

No. Every function has access to its lexical scope. Returning a function simply makes the preserved access especially visible because it runs after the outer function returns.

Do closures copy variables?

No. They retain access to bindings. If the binding changes, a closure reading it later can observe the new value.

Why are closures important in JavaScript interviews?

They connect core language concepts to callbacks, async code, loops, module design, private state, and React behavior.

Continue practising with JS Interview Prep

Use the JavaScript interview questions guide for topic-wise revision, then apply the ideas in the interactive practice area. Reading an answer is useful; explaining it and writing it under time pressure builds interview confidence.

Official references

Buy Me A Coffee