Closures
Question Variations
- "What is a closure in JavaScript, and can you give a real-world example?"
- "How do closures work with the garbage collector?"
- "Explain why using `var` in a loop with a closure often leads to unexpected results."
- "How can you use closures to implement private variables in a JavaScript class or module?"
Why This Is Asked
Closures are fundamental to functional programming patterns in JavaScript. Interviewers want to see if you understand lexical scoping, can identify closure-related bugs (especially in loops), and know practical applications like data privacy and partial application.
Key Concepts
- A closure is a function that retains access to its enclosing scope’s variables even after that scope has exited
- Created every time a function is defined, not when it’s executed
- Classic pitfall: closures over
varin loops capture the same variable reference - Practical uses: module pattern, memoization, currying, event handler factories
- Memory implications: closed-over variables are not garbage collected while the closure exists
Question Variations
- “What is a closure in JavaScript, and can you give a real-world example?”
- “How do closures work with the garbage collector?”
- “Explain why using
varin a loop with a closure often leads to unexpected results.” - “How can you use closures to implement private variables in a JavaScript class or module?”
Answers by Technology
+ Add VariantExpected Answer (Java 26)
In Java, closures are implemented via Lambda Expressions and Anonymous Inner Classes.
- Lexical Scoping: A lambda can access variables from its surrounding block.
- Effectively Final: In Java, a closure can only capture local variables that are
finalor “effectively final” (not modified after initialization). - Functional Interfaces: Lambdas are mapped to interfaces with a single abstract method (SAM), such as
Predicate,Function, orConsumer.
Why It Matters
Java’s closure implementation is unique because it forces developers to be explicit about state changes. The “effectively final” rule prevents race conditions where multiple threads might attempt to modify the same local variable within a closure.
Code Example
int limit = 10;
// 'limit' is effectively final
List<Integer> numbers = List.of(1, 5, 12, 8);
List<Integer> filtered = numbers.stream()
.filter(n -> n > limit) // Closure capturing 'limit'
.collect(Collectors.toList());
// limit = 20; // This would break the closure above!
Common Mistakes
- Modifying captured variables: Attempting to change a local variable used inside a lambda will result in a compilation error.
- Confusion with ‘this’: Inside a lambda,
thisrefers to the enclosing class instance, whereas in an anonymous inner class, it refers to the anonymous class itself.
Follow-up Questions
- Why must captured variables be effectively final? (Answer: To avoid synchronization issues, as the local variable is on the stack while the closure might execute on a different thread later).
- Method References: How do they relate to lambdas? (Answer: Syntactic sugar for lambdas that just call an existing method).
Expected Answer (PHP 8.5)
In PHP, closures are implemented as anonymous functions (objects of the Closure class).
- Lexical Scoping (
use): Unlike JavaScript, PHP closures do not automatically capture variables from the outer scope. You must explicitly pass them using theusekeyword. - By Value vs By Reference: By default, variables are captured by value. To modify an outer variable, you must pass it by reference (
use (&$variable)). - Arrow Functions (PHP 7.4+): Short syntax
fn($x) => $x + $ywhich does capture variables from the outer scope by value automatically.
Why It Matters
PHP’s requirement for the use keyword makes scope management explicit. It prevents accidental variable captures and makes it clear what data the closure depends on.
Code Example
$multiplier = 2;
// Explicit capture by value
$double = function($number) use ($multiplier) {
return $number * $multiplier;
};
// Arrow function (automatic capture by value)
$triple = fn($number) => $number * 3;
echo $double(5); // 10
Common Mistakes
- Forgetting the
usekeyword: Attempting to access an outer variable withoutusewill result in an “Undefined variable” error. - Expecting by-reference by default: Changing a variable inside a standard closure doesn’t affect the outer variable unless
&$variableis used.
Follow-up Questions
- Can closures be bound to objects? (Answer: Yes, using
$closure->bindTo($object)to change the value of$thisinside the closure). - Static Closures: Why use
static function() {}? (Answer: To prevent the closure from automatically binding the current class instance to$this, saving memory).
Expected Answer
A closure is a function that retains access to variables from its enclosing lexical scope, even after that outer function has returned. In JavaScript, closures are created every time a function is defined. It is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
Why It Matters
Closures are fundamental to the JavaScript language. They enable powerful patterns like data privacy (encapsulation), partial application (currying), and maintaining state in asynchronous callbacks or event handlers. Without closures, we couldn’t have private variables in functional programming or effectively handle state in React hooks like useState.
Example Code
Data Privacy (Module Pattern)
function createCounter() {
let count = 0; // This variable is "closed over" and private
return {
increment: () => ++count,
getCount: () => count,
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2 — `count` persists
console.log(counter.count); // undefined (private!)
The Classic Loop Pitfall
// Bug: all callbacks log 3 because `var i` is function-scoped
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
} // Logs: 3, 3, 3
// Fix: `let` creates a new binding per iteration
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
} // Logs: 0, 1, 2
Common Mistakes
- Closure over
varin loops: Each iteration shares the samevarbinding, so all closures see the final value. Usingletor an IIFE fixes it. - Memory leaks: Closures hold references to their entire enclosing scope. If a closure references a large object, it can’t be garbage collected as long as the closure exists.
- Accidental stale closures in React: Using a value inside a
useEffectcallback that was captured at render time but has since changed.
Follow-up Questions
- How does the garbage collector handle variables captured by closures? (Answer: The GC cannot collect any variable referenced by an active closure. If the closure is stored in a long-lived structure like an event listener, the closed-over variables persist).
- What is the difference between a closure and an IIFE? (Answer: An IIFE is a function that executes immediately and can create a closure. IIFEs are a pattern for creating isolated scopes; closures are a language mechanism).
References
Expected Answer (Python 3.14)
Python resolves a bare name in LEGB order: local, enclosing function scopes, global module scope, and built-ins. An assignment inside a function creates a local name by default. Use nonlocal to rebind a name from an enclosing function and global to rebind a module-level name, although passing state explicitly is often clearer.
Closures capture bindings, not a snapshot of a loop variable’s value. Bind the value through a default argument or a helper function when each callback needs its own value.
def make_counters():
total = 0
def increment():
nonlocal total
total += 1
return total
return increment
counter = make_counters()
assert counter() == 1
assert counter() == 2
callbacks = [lambda item=item: item for item in range(3)]
assert [callback() for callback in callbacks] == [0, 1, 2]
Why It Matters
Correct scope handling prevents callbacks with surprising values and state updates that silently affect the wrong variable. It also makes closures and decorators easier to debug and test.
Common Mistakes
- Assigning to an outer name without
nonlocal: Python treats it as a new local and can raiseUnboundLocalErrorwhen it is read first. - Using
globalas a shortcut for shared state: It makes dependencies harder to test and reason about. - Expecting a closure to snapshot a changing loop variable: It looks up the enclosing binding when called unless a value is bound explicitly.
Follow-up Questions
- What scope does a comprehension variable have in Python 3? (Answer: It is local to the comprehension and does not leak into the surrounding scope.)
- How can a closure retain one loop value? (Answer: Bind it in a default parameter or pass it to a function that creates the closure.)
Expected Answer (C++23)
A C++ lambda is an object with an operator() that can capture variables from its enclosing scope. Capture by value copies the captured value into the closure object; capture by reference stores a reference and therefore requires the referenced object to outlive every invocation of the lambda.
Choose capture mode deliberately. Default capture forms ([=] and [&]) can hide which values and lifetimes the closure depends on, especially when it is stored or runs asynchronously.
#include <functional>
#include <string>
std::function<std::string()> make_greeting(std::string name) {
return [name = std::move(name)] {
return "Hello, " + name;
};
}
int main() {
auto greeting = make_greeting("Ada");
return greeting() == "Hello, Ada!" ? 0 : 1;
}
Why It Matters
Captures determine whether a closure owns its data or borrows it. Correct capture lifetimes prevent dangling references in callbacks, threads, and deferred work.
Common Mistakes
- Capturing a local variable by reference and returning the lambda: The reference dangles after the function returns.
- Using
[&]in asynchronous work without a lifetime guarantee: The task may run after the referenced objects are destroyed. - Assuming capture by value always avoids copies: Init-capture with
std::movecan transfer an owned value into the closure.
Follow-up Questions
- What does
[this]capture? (Answer: It captures thethispointer, not a copy of the whole object.) - When should a lambda be
mutable? (Answer: When it captures by value and needs to modify its own captured copy.)