Closures in Python

A closure is a function that retains access to bindings from an enclosing scope, even after that scope has returned. In Python, closures support callbacks and small stateful functions, while `nonlocal` enables rebinding an enclosing function's name.

A closure is a function that can use names from an enclosing function scope, even when execution has returned from that enclosing call. In Python, this commonly happens when an inner function is returned or otherwise kept alive.

The function retains access to the relevant bindings, not necessarily a frozen copy of each value. If a captured mutable object changes, the closure can observe that change. Python uses lexical scope: the enclosing function is determined by where the function is defined, not where it is called.

A nested function can read an enclosing binding directly. To rebind that name in the enclosing function's scope, declare it `nonlocal`; assigning without `nonlocal` makes it local to the nested function. Closures are useful for small stateful functions, callbacks, and decorators, though a class may be clearer when state or behavior grows.

Key points

Interview questions

What is a closure, and how does Python use one?

A closure is a function that retains access to names from its enclosing lexical scope. This lets an inner function use values from an outer function after the outer call has finished. In Python, the retained access is to bindings, so changes to a captured mutable object remain visible. The function's `__closure__` can expose cell objects for captured free variables.

Why can lambdas created in a loop all return the same value, and how do you fix it?

In a list comprehension, lambdas created in a loop can all refer to the same loop variable binding, which has its final value when the lambdas are called later. A common workaround is to bind the current value as a default argument, such as `lambda i=i: i`. A factory function that takes the current value and returns a lambda also creates a distinct enclosing binding for each iteration. The default-argument technique binds the value at function definition time rather than creating a closure over a fresh loop-local scope.

How does `nonlocal` work, and when would you need it?

Reading a name from an enclosing function scope is allowed without a declaration. If the nested function assigns to that name, Python treats it as local by default, which can cause an `UnboundLocalError` when it is read before that local assignment. `nonlocal name` tells Python to rebind the nearest enclosing function-scope binding instead. It does not target module globals; use `global` for that case.

When is a closure a good way to encapsulate state, and when would you prefer another design?

A closure can encapsulate state by keeping a binding in an enclosing scope and exposing operations through the returned function. For example, a counter closure can increment a captured integer using `nonlocal`. This is convenient for a small amount of private state, but the behavior and state are less explicit than a class when there are multiple operations or invariants. Closures are not a general substitute for synchronization when shared across threads or tasks.