ReviseAlgo Logo

Functions

Closures

Functions that capture and remember their enclosing scope variables

Interview: Advanced concept frequently tested — basis for decorators, callbacks, and stateful functions

Last Updated: June 12, 2026 10 min read

A closure is a function that remembers the variables from its enclosing scope even after the enclosing function has finished executing. Closures are a powerful alternative to classes for maintaining state, and they form the foundation of decorators in Python.

Three Conditions for a Closure

  • Nested function: There must be a function defined inside another function
  • Captured variable: The inner function must reference a variable from the enclosing scope
  • Returned function: The enclosing function must return the inner function

How Closures Work Internally

  • Cell objects: Python stores captured variables in "cell" objects — accessible via func.__closure__
  • Snapshot of scope: The closure captures the variable's binding, not its value at creation time
  • Lifetime: The captured variables persist as long as the closure function exists
  • Memory: Closures keep a reference to the enclosing scope — can prevent garbage collection if overused

Closures vs Classes

Closures can replace simple classes that only have one method and store state. make_counter() returning a closure is equivalent to a Counter class with an __init__ and __call__ method. Closures are more lightweight and concise for simple stateful functions.

nonlocal and Closures

  • Read-only capture: Inner functions can READ enclosing variables without nonlocal
  • Modifying capture: To MODIFY an enclosing variable, you must declare nonlocal in the inner function
  • Without nonlocal: Assigning to a variable creates a new local variable — it doesn't modify the enclosing one

Practical Applications

  • Decorators: Every decorator is a closure — it captures the wrapped function
  • Factory functions: Create customized functions with captured configuration
  • Stateful callbacks: Maintain state between callback invocations without global variables
  • Memoization: Cache computed results in the enclosing scope
  • Partial application: Bind some arguments and return a function expecting the rest

Closure Late Binding

Closures capture variable NAMES, not values. This causes the classic loop bug: [lambda: x for x in range(3)] creates 3 functions that all return 2 (the final value of x). Fix by using default arguments: [lambda x=x: x for x in range(3)].

Use Cases

Implementing decorators that maintain state (call counts, caches, rate limiters)

Factory functions that create customized functions with captured configuration

Replacing simple classes with stateful functions (counters, accumulators)

Partial application — binding some arguments and returning a function for the rest

Memoization caches that persist across function calls

Common Mistakes

Late binding in loops — closures capture variable names, not values; use default args to fix

Forgetting nonlocal when modifying enclosing variables — creates a new local instead

Not understanding that closures capture bindings, not snapshots — the variable can change

Memory leaks — closures keep references to the entire enclosing scope, preventing garbage collection

Using closures when a simple class would be clearer — prefer readability for complex state