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.