Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Python›Closures

Functions

Closures

A closure is a function object that remembers values in its enclosing lexical scope even when the outer function has finished executing. They provide a powerful mechanism for data encapsulation and state preservation without relying on global variables or class instances. You use closures when you need to create specialized, reusable functions that maintain private configuration state across multiple calls.

Understanding Lexical Scoping

To understand closures, one must first grasp lexical scoping in Python. Lexical scoping means that the accessibility of variables is determined by their physical location within the source code during definition. When a function is defined, it creates a scope that can access its own local variables, but also the variables defined in its parent scope. This relationship is static; it does not change regardless of where or when the function is eventually called. When an inner function references a variable from an outer function, that variable becomes part of the inner function's 'closure'. Even after the outer function finishes executing and its own local scope is destroyed, the inner function retains a reference to those specific variables in a special attribute called __closure__. This effectively allows the inner function to 'carry' its environment with it, maintaining access to the outer variables as if they were still in scope during every invocation.

# The inner function 'greet' has access to 'prefix' defined in 'make_greeter'
def make_greeter(prefix):
    def greet(name):
        # 'prefix' is accessed from the enclosing scope
        return f"{prefix}, {name}!"
    return greet

# 'morning_greeter' is a closure holding 'prefix' as 'Good morning'
morning_greeter = make_greeter("Good morning")
print(morning_greeter("Alice"))

Capturing State via Non-local Variables

Closures go beyond simple read-only access; they allow for stateful operations by capturing variables. If an inner function needs to modify a variable defined in the outer scope, we must use the 'nonlocal' keyword. Without this keyword, attempting to assign a value to a variable from an outer scope would simply create a new local variable within the inner function, shadowing the outer one. By explicitly declaring a variable as 'nonlocal', you instruct the interpreter to look for that variable in the nearest enclosing scope and bind it for modification. This creates a powerful pattern where the inner function acts as a persistent state manager. Each time the closure is invoked, it updates the captured state, and those changes persist for the next call. This mechanism is essentially a lightweight alternative to creating an entire class just to maintain a single counter or a specific configuration state, making your code significantly more concise.

# A counter closure using 'nonlocal' to update captured state
def create_counter():
    count = 0  # This variable is captured
    def increment():
        nonlocal count  # Required to modify the outer variable
        count += 1
        return count
    return increment

counter = create_counter()
print(counter()) # Returns 1
print(counter()) # Returns 2

Encapsulating Logic with Closures

Encapsulation is the practice of hiding internal state and requiring all interaction to occur through a public interface. Closures facilitate this perfectly because the captured environment variables are entirely inaccessible from outside the function. They cannot be modified or inspected directly by any external code, providing a clear boundary for your logic. This 'private' data remains safely isolated within the function's scope, ensuring that the integrity of the state is maintained throughout the object's lifecycle. By returning only the inner function, you expose a clean interface to the user, while the internal complexity and state tracking remain hidden. This approach is highly effective for building modular components, such as API wrappers or data processing pipelines, where specific configuration parameters need to be 'baked in' at creation time without cluttering the global namespace or requiring the overhead of complex class definitions or inheritance hierarchies.

# Encapsulating a power-raising function
def power_factory(exponent):
    # 'exponent' is encapsulated and hidden from the outside
    def raise_to(base):
        return base ** exponent
    return raise_to

square = power_factory(2)
cube = power_factory(3)
print(square(4)) # 16
print(cube(4))   # 64

The Trap of Late Binding

A common mistake when working with closures involves 'late binding'. This occurs when a closure captures a variable by reference rather than by value, often inside a loop. If multiple closures are created in a loop and all refer to the same loop variable, they will all see the final value assigned to that variable when they are executed. This is because the variable is captured by its name in the outer scope, not by its snapshot value at the moment the closure is defined. To fix this, you must force the binding of the current variable value by using default arguments. By passing the loop variable as a default argument, the closure captures the value at the time of definition, creating a local copy within the inner function signature. Understanding this distinction between reference and value binding is critical for avoiding subtle, hard-to-track bugs when generating functions dynamically through iterations.

# Late binding trap: all functions will print 2
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2]

# The fix: capture by default argument
fixed_funcs = [lambda x=i: x for i in range(3)]
print([f() for f in fixed_funcs]) # [0, 1, 2]

Closures vs Classes

Choosing between a closure and a class depends largely on the complexity of your requirements. Closures are ideal for simple, single-method objects that require only a small amount of state to function. They are lightweight, memory-efficient, and easy to define on the fly, which makes them perfect for functional programming styles and decorators. However, if your object needs to expose multiple methods that modify the same state, or if the internal state management becomes complex, a class is the superior choice. Classes provide a more structured approach with explicit state definition, better debugging support, and inheritance capabilities. A class-based structure makes the relationship between data and behavior transparent, which is essential for large, maintainable systems. Use closures for localized behavior and quick state capture, but pivot to classes when your logic grows to include multiple behaviors and extensive object interaction.

# A class compared to a closure
class Counter:
    def __init__(self): self.count = 0
    def __call__(self): self.count += 1; return self.count

# Closures are usually shorter for single-function tasks
counter_obj = Counter()
print(counter_obj()) # 1

Key points

  • A closure is a function that retains access to its lexical scope even after its parent function has returned.
  • Lexical scoping allows functions to resolve variable names based on their definition site.
  • The nonlocal keyword is necessary to modify variables defined in an enclosing scope.
  • Closures encapsulate private state, preventing unauthorized access from outside the inner function.
  • Late binding occurs because closures reference variables in the outer scope rather than their values at definition time.
  • You can resolve late binding issues by using default arguments to capture values during definition.
  • Closures provide a concise, lightweight alternative to classes for simple stateful logic.
  • Classes should be preferred over closures when the state and logic become overly complex or require multiple methods.

Common mistakes

  • Mistake: Expecting a loop variable to be bound to its current value inside a closure. Why it's wrong: Python closures use late binding, meaning they look up the variable value when the function is called, not when defined. Fix: Use a default argument in a lambda or inner function, e.g., 'lambda x=i: x'.
  • Mistake: Trying to modify an outer scope variable without the 'nonlocal' keyword. Why it's wrong: Assignment creates a new local variable by default, causing an UnboundLocalError if you try to read it before assignment. Fix: Use the 'nonlocal' keyword to explicitly reference the outer scope variable.
  • Mistake: Misunderstanding the lifetime of the outer function. Why it's wrong: Beginners often assume the closure disappears when the outer function finishes executing. Fix: Understand that Python uses garbage collection to keep the closure's environment alive as long as the inner function exists.
  • Mistake: Returning the result of a function instead of the function itself. Why it's wrong: A closure is a function object; returning a call like 'return inner()' returns the value, not the closure. Fix: Return the inner function object without parentheses.
  • Mistake: Treating mutable objects in a closure as if they are re-assigned. Why it's wrong: You can mutate the contents of a list or dictionary from a closure without 'nonlocal' because you aren't re-assigning the name itself. Fix: Understand the difference between name rebinding and object mutation.

Interview questions

What exactly is a closure in Python?

A closure is a function object that remembers values in enclosing scopes even if they are not present in memory. It occurs when a nested function references a variable from its outer, non-global scope. For this to happen, the outer function must return the inner function. A simple example is: 'def outer(x): def inner(y): return x + y; return inner'. Here, 'inner' is a closure because it captures the variable 'x' from its parent's environment.

How does Python maintain the state of variables in a closure?

Python maintains the state using the '__closure__' attribute of the function object. When a nested function references a variable from an enclosing scope, Python creates a 'cell' object to store that variable. This cell object allows the inner function to access the value even after the outer function has finished executing and its local namespace has been destroyed. Essentially, the closure holds a reference to the cell containing the variable, preventing it from being garbage collected.

Can you modify a variable from the enclosing scope inside a closure?

By default, you can read variables from an enclosing scope, but you cannot modify them. If you try to assign a new value to a variable defined in the outer scope, Python will treat it as a local variable for the inner function, leading to an UnboundLocalError. To explicitly modify an outer variable, you must use the 'nonlocal' keyword, which tells Python to bind the variable to the nearest enclosing scope instead of the current local scope.

Compare using a class with a __call__ method versus using a closure to maintain state.

Both approaches encapsulate state, but they differ in implementation and use cases. A class is more explicit and scales better if you need multiple methods to manipulate that state, as you store values in 'self'. A closure is more lightweight and functional in style, hiding the state behind a single function call. Closures are often preferred for simple tasks like decorators, while classes are better for complex objects requiring private state or multiple interacting methods.

What is a common pitfall when creating closures inside a loop in Python?

A common pitfall is the 'late binding' issue. If you define a closure inside a loop that references a loop variable, the closure captures the variable itself, not its current value at the time of definition. Consequently, all closures will reference the final value of the loop variable. To fix this, you must use a default argument, such as 'lambda i=i: i', to force the closure to capture the value at definition time.

How do closures facilitate the implementation of decorators in Python?

Decorators are a prime application of closures. A decorator is a function that takes another function as an argument and returns a new function (the closure) that wraps the original. Because the wrapper function is a closure, it can capture the original function as part of its environment. This allows the wrapper to execute code before or after the original function is called, while keeping the original function accessible, thereby extending behavior without modifying the source code directly.

All Python interview questions →

Check yourself

1. What is the result of executing this code: def make_adder(n): return lambda x: x + n; add5 = make_adder(5); print(add5(10))?

  • A.10
  • B.15
  • C.5
  • D.None
Show answer

B. 15
The correct answer is 15. The lambda captures 'n' from the outer scope, creating a closure that adds 5 to the input 10. Other options are mathematically incorrect based on the closure's state.

2. Why does a standard loop inside a function creating closures often result in all closures using the final value of the loop variable?

  • A.Python's garbage collector clears the variable too early.
  • B.The loop variable is not in the scope of the closure.
  • C.Closures look up the variable name in the outer scope at execution time.
  • D.The 'nonlocal' keyword is required for every loop iteration.
Show answer

C. Closures look up the variable name in the outer scope at execution time.
Closures use late binding; they look up the value of the variable in the parent scope when they are finally executed, by which time the loop has finished and the variable holds the last value. This is why the other options are incorrect.

3. Which keyword is required to modify an integer variable defined in the enclosing function from within a nested function?

  • A.global
  • B.static
  • C.nonlocal
  • D.inner
Show answer

C. nonlocal
The 'nonlocal' keyword tells Python to search the nearest enclosing scope for the variable rather than creating a local one. 'global' references module level, and the others are not valid keywords for this purpose.

4. Consider: def outer(): x = 0; def inner(): x += 1; return x; return inner. Why will this raise an UnboundLocalError?

  • A.Because x is not defined in the global scope.
  • B.Because x is being read before assignment inside inner, but assignment makes it local.
  • C.Because the inner function cannot access x at all.
  • D.Because integers are immutable and cannot be updated in closures.
Show answer

B. Because x is being read before assignment inside inner, but assignment makes it local.
When you try to increment 'x', Python interprets 'x' as a local variable for 'inner', but it hasn't been defined yet, hence the error. 'global' is irrelevant here, and integers can be modified with 'nonlocal', not just by assignment.

5. What happens to the closure's 'environment' (the variables it captures) after the outer function finishes executing?

  • A.It is immediately garbage collected.
  • B.It is persisted in a special __closure__ attribute.
  • C.It is destroyed if the inner function is not returned.
  • D.It is copied into the global scope.
Show answer

B. It is persisted in a special __closure__ attribute.
Python keeps the captured variables alive in the __closure__ attribute of the function object. It is not destroyed as long as the reference to the inner function exists, it is not copied to global, and it is not immediately collected.

Take the full Python quiz →

← PreviousScope, Global, and Local VariablesNext →Lists — Creation and Operations

Python

78 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app