Skip to main content

Python Closures: Functions with Memory and State

A closure is a function that captures and retains variables from its enclosing scope even after that scope has exited. Closures enable stateful functions without global variables or classes, and they form the foundation of decorators and function factories. Understanding closures is essential for mastering advanced Python patterns.

What Is a Closure and How Does It Work?

A closure occurs when a nested function is returned from its outer function and continues to reference variables from that outer function's scope. The nested function "closes over" those variables, keeping them alive in memory. This allows the returned function to maintain state between calls—a powerful alternative to classes for simple stateful behavior.

When you define a function inside another function, the inner function can access variables from the outer function's scope due to Python's LEGB (Local, Enclosing, Global, Built-in) scope resolution order. However, a closure forms specifically when the inner function is returned and executed outside the outer function's context.

def outer_function(text):
"""Outer function with a local variable."""

def inner_function():
"""Inner function accessing enclosing scope."""
print(text)

return inner_function

# Call outer_function; it returns inner_function
my_closure = outer_function("Hello, closure!")

# Execute the closure; it still remembers 'text'
my_closure() # Output: Hello, closure!

print(type(my_closure)) # Output: <class 'function'>

The critical insight: even though outer_function has finished executing, the variable text remains accessible to the returned inner_function. Python preserves the enclosing scope in a closure object's __closure__ attribute.

Inspecting a closure's captured variables:

def create_adder(x):
def add(y):
return x + y
return add

add_5 = create_adder(5)

# Inspect captured variables
print(add_5.__closure__) # Shows the cell objects
print(add_5.__closure__[0].cell_contents) # Output: 5

print(add_5(3)) # Output: 8

How Do Nested Functions Enable Closure Behavior?

Nested functions are prerequisites for closures. An inner function defined inside an outer function can access variables from the outer function's scope—this is the mechanism that enables closure capture. When you return the inner function from the outer function, Python preserves the outer function's local scope (in closure cells) so the inner function can continue accessing those variables.

Basic nested function example (without closure):

def greet(name):
greeting = "Hello, "

def say_hello():
# This inner function accesses 'greeting' and 'name' from enclosing scope
return greeting + name

# Call the inner function immediately
return say_hello()

result = greet("Alice") # Output: Hello, Alice

In this example, inner_function executes immediately, so the enclosing scope is still active. The nested function accesses the enclosing variables while the outer function runs.

Creating a true closure (returning the function, not calling it):

def greet_factory(name):
greeting = "Hello, "

def say_hello():
# Now this function is returned, not called
return greeting + name

# Return the function itself
return say_hello

# Create a closure
greet_alice = greet_factory("Alice")

# The closure retains access to 'name' even though greet_factory has exited
print(greet_alice()) # Output: Hello, Alice

The difference is subtle but profound: returning the function creates a closure that persists beyond the outer function's execution.

How Does the nonlocal Keyword Enable State Modification?

The nonlocal keyword allows an inner function to modify variables from the enclosing scope. Without nonlocal, Python interprets assignment statements as creating new local variables, not modifying outer scope variables. This is where nonlocal becomes essential for stateful closures.

Problem: attempting to modify without nonlocal

def make_counter():
count = 0

def increment():
count = count + 1 # This creates a NEW local 'count', causing UnboundLocalError
return count

return increment

counter = make_counter()
counter() # UnboundLocalError: local variable 'count' referenced before assignment

Python sees count = ... and assumes count is a local variable. The expression count + 1 tries to read count before it's assigned locally, causing an error.

Solution: use nonlocal to modify enclosing scope

def make_counter():
count = 0

def increment():
nonlocal count # Tell Python to use 'count' from enclosing scope
count += 1
return count

return increment

counter = make_counter()
print(counter()) # Output: 1
print(counter()) # Output: 2
print(counter()) # Output: 3

The nonlocal count statement explicitly declares that the count variable being modified belongs to the enclosing scope, not the local scope. Without nonlocal, modifications within a nested function create new local variables, isolating them from the enclosing scope (Python Software Foundation, 2025).

How Do You Create Stateful Functions Using Closures?

Closures excel at creating stateful functions—functions that remember values across calls without classes or global state. This pattern is cleaner than managing global variables and more concise than using classes for simple state.

Example: function factory for counters

def make_counter(start=0):
"""Factory that creates independent counter functions."""
count = start

def increment():
nonlocal count
count += 1
return count

def decrement():
nonlocal count
count -= 1
return count

def get_value():
return count

return {"increment": increment, "decrement": decrement, "get": get_value}

# Create independent counters with separate state
counter1 = make_counter(0)
counter2 = make_counter(100)

print(counter1["increment"]()) # Output: 1
print(counter1["increment"]()) # Output: 2
print(counter1["get"]()) # Output: 2

print(counter2["decrement"]()) # Output: 99
print(counter2["get"]()) # Output: 99

Each call to make_counter() creates a new enclosing scope with its own count variable. Each returned closure captures that specific count, maintaining independent state.

Real-world example: rate limiter using closures

def rate_limiter(max_calls, period_seconds):
"""Create a rate-limiting function."""
calls = []

def allow_call():
nonlocal calls
import time
now = time.time()

# Remove calls older than the period
calls = [call_time for call_time in calls if now - call_time < period_seconds]

if len(calls) < max_calls:
calls.append(now)
return True
return False

return allow_call

# Create a limiter: max 3 calls per 10 seconds
limiter = rate_limiter(3, 10)

for i in range(5):
if limiter():
print(f"Call {i+1}: allowed")
else:
print(f"Call {i+1}: rate limited")

Closures vs. Classes: When to Use Each

Both closures and classes can maintain state, but each is suited for different scenarios:

AspectClosureClass
SimplicitySimple state, few methodsMultiple state variables, many methods
ReadabilityConcise for single-purpose functionsExplicit when state/behavior is complex
ReusabilitySpecialized state capturedPolymorphism, inheritance
MemoryLightweight; captures only needed variablesObject overhead

Use closures for lightweight, single-purpose stateful behavior; use classes for complex objects with multiple methods and inheritance.

Key Takeaways

  • A closure is a function that captures variables from its enclosing scope and retains them after the outer function exits.
  • Closures require nested functions returned from their parent function, enabling state capture.
  • The nonlocal keyword allows modifying captured variables from an enclosing scope within an inner function.
  • Closures provide a clean way to create stateful functions without global variables or classes.
  • Closures are the foundation of decorators and function factories, key patterns in advanced Python.

Frequently Asked Questions

Can a closure capture multiple variables?

Yes. An inner function captures all variables from its enclosing scope that it references:

def make_multiplier(a, b):
def multiply(x):
return (a + b) * x
return multiply

f = make_multiplier(2, 3)
print(f(4)) # Output: 20 (captures both a and b)

What's the difference between a closure and a lambda?

A lambda is a concise syntax for defining anonymous functions; a closure is a pattern where an inner function captures outer scope. A lambda can be a closure if it captures enclosing variables, but not all closures are lambdas. Lambdas cannot use nonlocal.

Can closures cause memory leaks?

Closures retain references to captured variables, preventing garbage collection. In rare cases with large data structures, this can delay memory reclamation, but Python's garbage collector handles most circular references. Monitor for excessive closure creation in tight loops.

How do you inspect what variables a closure captures?

Use function.__closure__ to access closure cells and cell_contents to inspect captured values:

def outer(x):
def inner():
return x
return inner

f = outer(42)
print(f.__closure__[0].cell_contents) # Output: 42

Can you modify a closure captured variable from outside?

No. Captured variables are encapsulated. Modify them only via methods returned by the factory function that has access to them.

Further Reading