Topics
v6.0.0
On this page · sections

JavaScript Execution Context & Closures

Can you explain what a function still sees after it returns — and why that captured binding is not a snapshot?

Execution context and closures are the same story: every call creates an environment, and inner functions keep a link to it.

These questions test:

  • Call stack vs lexical environment
  • Creation phase vs execution phase
  • Capturing the binding, not the value
  • Private state and lifetime
  • Memory and stale closures in React

Questions

Context

  1. What Is an Execution Context?
  2. Call Stack & Recursion

Closures

  1. What Is a Closure?
  2. The var-in-a-Loop Bug

Senior-Level Follow-ups

  1. Private State With Closures
  2. Does a Closure Keep the Whole Environment Alive?
  3. Stale Closures in React

Context

1. What Is an Execution Context?

Why This Matters

Hoisting, this, and closures all follow from how the engine builds an environment before running the first line.

Problem

What is created when a function is called, and how are identifiers resolved?

Example

function outer() { const x = 10; function inner() { console.log(x); } inner(); } outer();
Creation phase → variable / lexical environment → this binding → hoist function declarations Execution phase → run statements → resolve names by walking the outer chain

Identifier lookup walks:

current environment → outer → … → module / global

That chain is lexical. It is determined by where the function was written, not by who called it.

Interview Answer

“An execution context is the environment for one run of a function or script. It holds bindings, this, and an outer reference. The call stack tracks which context is running. Name resolution walks the lexical chain, not the caller chain.”

2. Call Stack & Recursion

Why This Matters

Synchronous recursion without a base case throws RangeError: Maximum call stack size exceeded. Deep JSON or tree walks hit the same limit.

Problem

What does the stack do on a recursive call, and should you rely on tail-call optimization?

Example

function factorial(n) { if (n <= 1) return 1; return n * factorial(n - 1); } factorial(5);

Each call pushes a frame. Return pops it.

Tail-call reuse exists in the spec. Engines rarely apply it in practice. For unbounded depth, use an explicit stack or iteration.

Interview Answer

“The call stack is LIFO. Recursion costs a frame per call. I do not depend on TCO in browsers. For deep traversal I use an explicit stack so memory is data, not call-stack.”

Closures

3. What Is a Closure?

Why This Matters

The interview definition is not “a function inside a function.” It is a function plus the lexical environment it was created in.

Problem

Does a closure capture the value or the variable? Give a production example.

Example

function makeCounter() { let count = 0; return { inc() { return ++count; }, read() { return count; }, }; } const a = makeCounter(); const b = makeCounter(); a.inc(); a.inc(); b.inc(); console.log(a.read(), b.read()); // 2 1

Each call to makeCounter creates a new environment.

Captures the binding:

let value = 1; const read = () => value; value = 2; read(); // 2

To freeze a value, copy it into a new binding:

const readFrozen = ((snapshot) => () => snapshot)(value);

Key Idea

Closure = function object + [[Environment]] 1. It captures the BINDING, not a snapshot 2. It is created whenever an inner function uses an outer name

Interview Answer

“A closure is a function that retains its lexical environment. It sees later assignments to that binding. Two factory calls do not share state unless they close over the same environment.”

4. The var-in-a-Loop Bug

Why This Matters

This is still the most common closure output question.

Problem

Why do three timeouts print 3, and how do you fix it?

Example

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

Fixes:

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

Interview Answer

“var i is one function-scoped binding. Every callback reads the final value. let gives each iteration its own binding. An IIFE with var copies the value into a new parameter.”

Senior-Level Follow-ups

5. Private State With Closures

Closures are how JavaScript had private fields before #:

function createWallet(opening) { let balance = opening; return { deposit(n) { balance += n; }, getBalance() { return balance; }, }; }

balance is unreachable except through the returned methods. That is encapsulation, not a security boundary against hostile code in the same realm.

Class #fields are the language-level version. Closures still win for factories and one-off APIs.

6. Does a Closure Keep the Whole Environment Alive?

In theory the environment record stays reachable. Engines may optimize unused bindings, but you must not rely on that.

function load() { const huge = new Array(1e6).fill("x"); const id = 1; return () => id; }

If huge is not used by the returned function, some engines can drop it. If anything in the closure graph still mentions it, it stays.

For caches and event handlers, assume closed-over objects live as long as the function.

7. Stale Closures in React

function Counter() { const [n, setN] = useState(0); useEffect(() => { const id = setInterval(() => { console.log(n); }, 1000); return () => clearInterval(id); }, []); }

The interval closes over the first n. It will log 0 forever.

Fixes: include n in deps, or read from a ref, or use the functional updater.

This is the same rule: the function captures the binding from the render that created it.

Final Interview Strategy

1. Context = environment for one run 2. Stack tracks contexts; lexical chain resolves names 3. Closure = function + environment 4. Capture is by binding, not snapshot 5. var loop vs let loop is one vs many bindings 6. Closed-over data has lifetime cost 7. React stale props/state are the same closure rule

Senior-level signal: Define closure precisely, then connect it to memory, privacy, and UI staleness — not only to the loop puzzle.

Last updated on