Stateful Functions Using Closures

RoadmapsPython

Overview

A stateful closure is a function that remembers and updates data between calls, without using global variables or a full class. It does this by using the `nonlocal` keyword to modify variables trapped in its enclosing scope. Think of it like a function carrying around a hidden, persistent backpack of data. This allows you to encapsulate state perfectly within a single callable. It is commonly used to build lightweight state machines or function factories. When applying stateful closures to wrap multiple functions, reusing the same factory instance accidentally links their internal state. This shared-state trap is covered fully in decorators. shared_limiter = make_rate_limiter() @shared_limiter def fetch(): pass @shared_limiter def post(): pass # Trap: fetch and post now share the exact same call count!

It provides a lightweight alternative to writing a `class` when you only need a single function that remembers some history, like a counter or an accumulator. It keeps state strictly private and avoids polluting the global namespace.

Where used: FastAPI dependencies, Rate limiters, Event callbacks

Why learn this

Code walkthrough

def make_accumulator():
  total = 0
  def add(value):
    nonlocal total
    total += value
    return total
  return add

acc1 = make_accumulator()
print(acc1(5))
print(acc1(10))

Focus: nonlocal total

Aha moment

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

c1 = make_counter()
c2 = make_counter()
c1()
print(c2())

Prediction: What does `print(c2())` output?

Common guess: 2

Each time `make_counter()` is called, it creates a brand new enclosing scope with its own separate `count` variable. `c1` and `c2` have completely independent backpacks of state, so `c2()` returns `1` regardless of what `c1` does.

Common mistakes

Glossary

encapsulate
To bundle data and the methods that operate on that data into a single, enclosed unit, keeping it safe from outside interference. Example: `counter = make_counter()`.
functional code
A style of programming that emphasizes using functions as the primary building blocks, avoiding shared state or side effects. Example: `total = sum(map(double, numbers))`.

Recall questions

Understanding checks

What is printed by this code?

`2`, `4`, `2`

Each call to `setup()` creates an independent closure state. `f1` increments its own `x` twice (`2`, `4`), while `f2` uses a fresh `x` (starting at `0`, becoming `2`).

Why does this closure raise an `UnboundLocalError`?

It is missing the `nonlocal balance` declaration.

Without `nonlocal`, Python treats the assignment `balance += amount` as creating a new local variable `balance` inside `add`, which hasn't been initialized yet.

Practice tasks

Build a Simple Counter

Write a function `make_counter()` that takes no arguments. It should return a function that, when called, returns an integer starting at `1` and incrementing by `1` on each subsequent call.

Challenge

Moving Average Closure

Create a function `make_averager()` that returns a function. The returned function should take a single numeric value as an argument, add it to a running series, and return the average (mean) of all values passed to it so far. Hint: You can use a mutable `list` in the closure without `nonlocal`, or use `nonlocal` with `sum` and `count` variables.

Continue learning

Previous: Closures

Previous: LEGB rule: variable scope in Python

Next: Function Decorators

Return to Python Roadmap