Closures & LEGB scope

RoadmapsPython Backend

Overview

A closure is a function that remembers variables from the scope where it was created, even after that outer scope has finished running. Think of it as a backpack the inner function carries: when the outer function returns, the inner function keeps the variables it packed. The inner function keeps a persistent reference to its enclosing scope's variables across calls, even after the outer function has returned.

Closures let you build functions with private, persistent state without a class, and they are the mechanism that makes decorators possible. Any time you 'configure' a function by wrapping it, a closure is doing the remembering.

Where used: FastAPI, Pydantic validators, micrograd, functools

Why learn this

Code walkthrough

def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c = make_counter()
print(c())
print(c())
print(c())

Focus: Watch `count` survive between calls — it lives in the closure, not a global.

Common mistakes

Glossary

scope
The specific area in code where a variable is recognized and can be used, e.g. `def f(): x=1` means `x` is in local scope.
decorator
A special function that wraps another function to modify or enhance its behavior without changing its code, e.g. `@app.get('/')`.

Recall questions

Understanding checks

What will the following code output?

30

The `multiplier` inner function captures the variable `n` from the enclosing scope. When `times3` is called with 10, it multiplies 10 by the captured value 3.

Why do you need the `nonlocal` keyword when modifying a captured variable inside a closure?

Without `nonlocal`, assigning a value to a variable inside the inner function creates a new local variable, rather than updating the variable in the enclosing scope.

Python's scoping rules dictate that any assignment makes a variable local to the current function. `nonlocal` explicitly tells Python to rebind the variable in the nearest enclosing scope.

Practice tasks

Closure counter

The `make_counter` function currently relies on a global variable. Modify it to use a closure by moving the `count` variable inside `make_counter` and using `nonlocal` inside the inner function.

Challenge

Implement a logging decorator

Using a closure, write a `@log_calls` decorator that prints the function name and arguments before calling it, then prints the return value. Preserve the wrapped function's name with functools.wraps.

Continue learning

Next: Decorators

Return to Python Backend Roadmap