Python/Functions/Closures

Closures

A closure is a function that remembers variables from the scope where it was defined, even after that scope has finished executing.

Advanced35mInterview Production

Learning objectives

  • Explain what a closure captures and how long it lives
  • Describe closures in terms of cell objects and __closure__
  • Recognise and fix the classic late-binding loop bug
  • Use closures to build decorators, callbacks and lightweight state

Prerequisites

FunctionsScope & the LEGB ruleNested functions

Version notes

  • · `nonlocal` (needed to *rebind* a captured variable) has existed since Python 3.0.

Simple explanation

What it is. A closure is the pairing of a function with the enclosing variables it references. When you define a function inside another function and return it, the inner function keeps a live link to the outer function's variables.

Why it exists. Python functions are first-class objects — you can return them and pass them around. For a returned inner function to still work, the variables it used must survive after the outer call returns. Closures are how Python keeps them alive.

The problem it solves. They let you attach private, persistent state to a function without a class, and they are the mechanism that makes decorators, callbacks, and factory functions possible.

How it works. CPython stores each captured variable in a shared `cell` object rather than on the stack frame. Both the outer and inner functions reference the same cell, so reads and writes stay in sync. You can inspect them via `inner.__closure__` and `func.__code__.co_freevars`.

Analogy: Think of a backpack the inner function carries around. When the function is created inside the outer scope, it packs references to the outer variables into its backpack and takes them wherever it goes — long after the outer function has gone home.

Common misconceptions

  • "A closure copies the variable's value." — It captures a reference to a cell, not a snapshot. The value is read when the inner function runs, not when it is defined.
  • "Each loop iteration gets its own captured value." — Not unless you bind it explicitly; see the late-binding example below.
  • "You need `nonlocal` to read an enclosing variable." — You only need it to *reassign* one.

Common alternatives

  • A class with __init__ storing state on self
  • functools.partial to pre-bind arguments
  • Default-argument binding to freeze a value at definition time

Interactive visualization

Step 1 / 6
1def make_multiplier(factor):
2 def multiply(n):
3 return n * factor
4 return multiply
5
6double = make_multiplier(2)
7print(double(10))

Call stack

<module>

make_multiplier

factor = → cell

Heap / objects

cellcell

factor = 2

refcount: 1

Call make_multiplier(2)

A new frame is pushed. Its local `factor` is bound to 2 — stored in a cell so an inner function can share it.

Code examples

BeginnerBasic closure
python
Loading editor…

Note: `double` and `triple` each carry their own cell holding a different `factor`.

Behind the scenes

  • Captured variables become `freevars`; CPython allocates a `cell` object for each and both frames point at the same cell.
  • `inner.__closure__` is a tuple of those cells; `cell.cell_contents` reveals the current value.
  • Because the cell is heap-allocated and referenced by the returned function, it survives after the outer frame is destroyed.
  • `nonlocal x` tells the compiler that assignments to `x` target the enclosing cell instead of creating a new local.

Interview insights

Key definitions

  • · Closure: a function bundled with references to the free variables from its defining scope.
  • · Free variable: a name used in a function that is neither local nor global — it comes from an enclosing scope.
What is a closure and when would you use one?

Concise answer: A function that remembers variables from its enclosing scope. Use it for factory functions, callbacks, decorators and small pieces of private state without a class.

Detailed answer: A closure forms when a nested function references variables from an outer function and is then used outside that outer function's lifetime. CPython keeps those variables alive in cell objects. Typical uses: decorators (wrapping behaviour around a function), configuration factories (make_multiplier), event callbacks that need context, and memoisation caches.

Common mistake: Saying a closure 'copies' the outer values. It captures references, so later changes are visible.

Follow-ups:

  • · How would you inspect what a closure captured?
  • · What is the difference between a closure and a partial?
Why does a loop that appends lambdas often print the same value?

Concise answer: All the lambdas share one cell for the loop variable, and they read it at call time — after the loop has finished — so they all see the final value.

Detailed answer: The closures capture the variable, not its value. The loop variable lives in a single cell reused each iteration. Fix it by binding the current value as a default argument (lambda i=i: ...) or by using a factory function that takes the value as a parameter.

Common mistake: Trying to fix it with a nested loop or copy() instead of binding.

Follow-ups:

  • · Does the same bug appear with functools.partial? (No — partial binds the value.)

Knowledge check

What's the output?Question 1 of 3

What does this print?

python
def outer():
    x = 10
    def inner():
        return x
    x = 20
    return inner
print(outer()())
Multiple choiceQuestion 2 of 3

Which keyword lets an inner function reassign an enclosing variable?

Multiple choiceQuestion 3 of 3

How can you inspect the values a closure captured?

Score: 0/3