Hello! Welcome back to our course.
In our last lesson, we explored nested functions and saw how an inner function can access and even modify variables in its enclosing scope using the nonlocal keyword. This was all happening while the outer function was still executing.
Today, we'll answer a fascinating question: what happens when the outer function finishes and returns, but we keep a reference to the inner function? This will lead us to the concept of closures, a powerful feature in Python that is fundamental to writing elegant, stateful functions and is the magic behind decorators.
Your goal is to understand idiomatic Python, and closures are a cornerstone of that. By the end of this lesson, you will be able to create and use closures that capture and maintain state from their enclosing scope.
1. What is a Closure?
Let's start with a formal definition.
Closure (computer programming)
The Wikipedia article on closures provides a precise, language-agnostic definition that is a great starting point.
Please read the first paragraph of the article. Focus on the idea of a closure being a function stored together with an 'environment' of its 'free variables' (variables from an enclosing scope).
In simpler terms, a closure is a function that "remembers" the environment in which it was created. It's a bundle: the inner function itself, plus a snapshot of the variables from the outer function's scope that the inner function needs.
This happens in three steps:
- You define a nested function (the inner function).
- The inner function refers to variables from the outer function.
- The outer function returns the inner function without executing it.
When the inner function is returned, Python packages it up with references to the variables it needs from the enclosing scope. This "package" is the closure.

2. Creating Closures to Retain State
Let's see why this is useful. Imagine you want a function that keeps a running list of numbers. A naive approach might use a global variable, but this breaks encapsulation and can lead to bugs. A closure provides a much cleaner solution.
The following video demonstrates this exact problem and presents a closure as the ideal solution.
What is a CLOSURE and how to use it in Python?
The video 'What is a CLOSURE and how to use it in Python?' by Coders Bring Change clearly illustrates the problem with global state and how a closure elegantly solves it.
Watch from the beginning until 03:35. Pay attention to the transition from a problematic global variable to a well-encapsulated closure.
As the video shows, the closure provides a private "state" for the inner function. Let's look at another classic example: a function factory for creating power functions.
def generate_power(exponent):
"""A factory for creating functions that raise to a specific power."""
def power(base):
"""The inner function that does the calculation."""
return base ** exponent
return power
# Create two different closures
raise_to_two = generate_power(2)
raise_to_three = generate_power(3)
# Use the closures
print(f"4 to the power of 2 is: {raise_to_two(4)}") # Output: 16
print(f"5 to the power of 2 is: {raise_to_two(5)}") # Output: 25
print(f"4 to the power of 3 is: {raise_to_three(4)}") # Output: 64
# raise_to_two and raise_to_three are independent functions.
# Each has its own remembered 'exponent'.
print(raise_to_two.__closure__[0].cell_contents) # Output: 2
print(raise_to_three.__closure__[0].cell_contents) # Output: 3
Here, raise_to_two and raise_to_three are two distinct closures. Each is a power function bundled with a different exponent value. The state is read-only but is perfectly preserved for each closure instance.
3. Modifying State Within a Closure
What if you need to change the captured state? This is where the nonlocal keyword from our previous lesson becomes essential. It tells the inner function to modify the variable in the enclosing scope, not create a new local one.
A counter is a perfect example of a closure with mutable state.

The following video provides an excellent animated explanation of how this works.
Closures in Python | Explained with animations
The video 'Closures in Python | Explained with animations' by Sreekanth gives a detailed walkthrough of a stateful counter, showing how nonlocal works to update the closure's state.
Watch from 03:01 to 04:54. Focus on how the start variable (a 'free variable') persists and gets updated across multiple calls to the closure.
The key is that nonlocal allows the inner function to reach out and modify the variable binding that was captured when the closure was created.
4. A Bridge from Your Java Background
The concept of a function capturing its environment might feel familiar. In Java, you've likely used anonymous inner classes, which exhibit similar behavior. This is a great parallel to draw.
Closure (computer programming)
The Wikipedia article on closures has a section dedicated to how Java implements closure-like behavior, which will directly connect to your experience.
Read the section 'Local classes and lambda functions (Java)'. Pay close attention to the requirement for captured variables to be final or 'effectively final'.
Let's summarize the comparison:
- Java's Approach: An anonymous class or lambda can access local variables from its enclosing method, but only if they are
finalor "effectively final" (i.e., never reassigned after initialization). This is essentially capturing by value. Java makes a copy of the variable's value for the inner class to use. - Python's Approach: A closure captures the binding of the variable, not just its value. This is closer to capturing by reference. It's why we can use
nonlocalto modify the original variable's value. The variable's lifetime is extended as long as the closure exists.
A great way to think about it is:
A Java object is data with methods attached. A Python closure is a function with data attached.
For simple cases where you need to bundle a single piece of behavior with some state, a closure is often more lightweight and "Pythonic" than defining a full class.
5. Practice Exercise: Running Average Calculator
Let's put this into practice. Your task is to create a function factory that produces a running average calculator.
- Define an outer function
make_averager(). - Inside
make_averager(), initialize two variables:count = 0andtotal = 0. - Define a nested function
averager(new_value)that will be your closure. - Inside
averager, use thenonlocalkeyword to modifycountandtotal. It should addnew_valuetototaland incrementcount. - The
averagerfunction should return the current average (total / count). make_averagershould return theaveragerfunction.- Test your factory by creating an averager and calling it multiple times with different numbers to see the average update.
Click here to see a possible solution
def make_averager():
"""A factory for creating running average calculators."""
count = 0
total = 0
def averager(new_value):
"""Calculates the running average."""
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
# --- Test sequence ---
avg = make_averager()
print(f"Adding 10, Current Average: {avg(10)}")
print(f"Adding 20, Current Average: {avg(20)}")
print(f"Adding 30, Current Average: {avg(30)}")
print(f"Adding 5, Current Average: {avg(5)}")
# Expected Output:
# Adding 10, Current Average: 10.0
# Adding 20, Current Average: 15.0
# Adding 30, Current Average: 20.0
# Adding 5, Current Average: 16.25
Conclusion
Today we've made a significant leap from simple nested functions to the powerful pattern of closures. This concept of a function that encapsulates both behavior and state is central to many advanced Python patterns.
Key Takeaways:
- A closure is a function bundled with its enclosing lexical environment, allowing it to "remember" the state from when it was created.
- Closures provide an elegant way to maintain state between function calls without polluting the global scope.
- The
nonlocalkeyword is used to modify the state captured by a closure. - Closures can be seen as a lightweight alternative to a single-method class for managing state, a useful perspective for someone with an OOP background.
In our next lesson, we'll wrap up this module by looking at Python's flexible argument-passing syntax, *args and **kwargs. After that, we'll see how closures form the very foundation of decorators, bringing us another step closer to fully understanding the code you set out to master.