Closures & Scope Interview Questions & Answers

5 questions Updated 2026-06-18

Python interview questions on closures and free variables, __closure__, the nonlocal keyword, late binding in loops and the default-argument fix, closures vs classes, and common closure uses.

A closure is a nested function that remembers variables from its enclosing scope even after that outer function has returned. The remembered names are called free variables — they're neither local parameters nor globals. Python stores them on the function's __closure__ attribute.

def multiplier(factor):
    def multiply(n):
        return n * factor      # 'factor' is a free variable
    return multiply

double = multiplier(2)
double(5)                      # 10
double.__closure__[0].cell_contents   # 2 — captured value

The inner function keeps the binding alive via a cell object, which is why multiplier can return and double still works. Closures are how Python functions carry private state without a class.

By default, assigning to a name inside a function creates a new local. nonlocal tells Python that an assignment should instead rebind a variable in the nearest enclosing function scope — letting a closure mutate, not just read, the captured variable.

def counter():
    count = 0
    def increment():
        nonlocal count        # rebind outer 'count'
        count += 1
        return count
    return increment

c = counter()
c(); c()                       # 1, then 2

Without nonlocal, count += 1 would raise UnboundLocalError (it reads then assigns a local). Use nonlocal for enclosing-function scope and global for module scope.

Closures capture variables, not values — this is late binding. A function created in a loop looks up the loop variable when it's called, not when it's defined, so every closure sees the variable's final value.

funcs = [lambda: i for i in range(3)]
[f() for f in funcs]           # [2, 2, 2]  — all see final i

# fix: bind the current value via a default argument
funcs = [lambda i=i: i for i in range(3)]
[f() for f in funcs]           # [0, 1, 2]

The default-argument trick works because defaults are evaluated at definition time, snapshotting i per iteration. A factory function that takes i as a parameter achieves the same. This is a favorite interview gotcha.

A function with free variables has a non-None __closure__ — a tuple of cell objects, each holding one captured binding accessible via cell_contents. The matching names are listed in __code__.co_freevars. Functions with no closure have __closure__ is None.

def make(x, y):
    def inner():
        return x + y
    return inner

f = make(3, 4)
f.__code__.co_freevars               # ('x', 'y')
[c.cell_contents for c in f.__closure__]   # [3, 4]

This is mostly useful for debugging or teaching how closures actually store state. The cells are shared live, so nonlocal rebinds are visible through cell_contents.

Both bundle behavior with state. A closure is lighter and ideal when you need a single method and a little hidden state. A class wins when you need multiple methods, inheritance, or explicit, inspectable state. Common closure uses include factories, decorators, and callbacks.

# closure: tiny stateful function
def make_adder(n):
    return lambda x: x + n
add10 = make_adder(10)

# class: equivalent but heavier
class Adder:
    def __init__(self, n): self.n = n
    def __call__(self, x): return x + self.n

Rule of thumb: one behavior + private state → closure; many behaviors or shared interface → class. Decorators are the canonical real-world closure.

Practice tests are coming soon

Get notified when interactive mock interviews and quizzes launch.