PyCodeItPython trace & interview prep
DashboardMastery MapSQL PracticeDailyInterviewCompaniesBlogLeaderboardCommunity
← Education Hub|python Guide

Python Closures Explained: How Free Variables Work in Interview Questions

Understand closure mechanics, free variable binding, and the cell object model that underpins Python's scoping rules - with worked interview examples.

AA

Ameer Abdullah

Data Science Graduate · AI/ML & Data Science

June 26, 2026·9 min read

A closure is a function that retains access to variables from its enclosing scope even after that scope has finished executing. When Python creates a closure, it stores references to the free variables (variables from the enclosing scope that the inner function uses) in a set of 'cell' objects attached to the inner function.

What Makes a Closure?

Interactive Trace Block
def make_multiplier(factor):
    def multiply(x):
        return x * factor  # 'factor' is a free variable
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))   # 10
print(triple(5))   # 15

In this example, multiply is a closure. After make_multiplier(2) returns, the local scope of that call is gone - but the inner multiply function still holds a reference to 'factor' through a cell object. The cell object keeps 'factor' alive as long as the closure exists. double.__closure__[0].cell_contents returns 2; triple.__closure__[0].cell_contents returns 3.

Late Binding: The Classic Gotcha

Interactive Trace Block
fns = []
for i in range(3):
    def f():
        return i
    fns.append(f)

print(fns[0]())  # 2, not 0!
print(fns[1]())  # 2
print(fns[2]())  # 2

All three functions close over the same cell object for 'i'. When any of them is called, they look up the current value of 'i' in the cell - which, after the loop finishes, is 2. This is 'late binding': the value is looked up at call time, not at definition time. It is one of the most common Python interview traps.

The Fix: Default Argument Capture

Interactive Trace Block
fns = []
for i in range(3):
    def f(i=i):  # default arg evaluated NOW
        return i
    fns.append(f)

print(fns[0]())  # 0
print(fns[1]())  # 1
print(fns[2]())  # 2

Using a default argument (i=i) forces Python to evaluate 'i' at function definition time and bind it to the default parameter value. Each function now has a different default for its 'i' parameter, so calling them without arguments returns 0, 1, and 2 respectively. This is the canonical Python fix for late-binding closure loops.

The nonlocal Keyword

Interactive Trace Block
def make_counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

c = make_counter()
print(c())  # 1
print(c())  # 2
print(c())  # 3

Without 'nonlocal count', the line 'count += 1' would raise an UnboundLocalError because Python would treat 'count' as a new local variable (since it appears on the left side of an assignment), but it has no value yet. The 'nonlocal' keyword tells Python to look for 'count' in the enclosing scope and modify it there, enabling stateful closures without using classes.

Quick Concept Quiz

In a closure created inside a for loop (e.g., def f(): return i), what value does f() return when called after the loop completes?

Ready to test your execution tracing skills?

Hop directly into the Python trace map to start coding, grading queries, and logging XP metrics to your workspace profile.

Continue learning

python

The Complete Guide to Tracing Python Code (With Examples)

Read guide →

python

10 Python Output Questions for Interview Preparation

Read guide →

python

How to Dry Run Python Code: Step-by-Step Method

Read guide →

Guide Contents

  • What Makes a Closure?
  • Late Binding: The Classic Gotcha
  • The Fix: Default Argument Capture
  • The nonlocal Keyword
PyCodeIt

Free interactive learning platform for Python code tracing, SQL queries, and technical interviews. Built for bootcamp grads, computer science students, and engineers.

Python Practice

  • Learning Center
  • For loop tracing
  • List tracing
  • Dictionary tracing
  • Decorators practice
  • Python Tracing Guide
  • Python Output Questions

SQL Practice

  • SQL Fundamentals
  • Relational JOINs
  • Window Functions
  • CTEs & Set Operators
  • SQL JOINs Guide
  • Window Functions Guide

Legal

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 PyCodeIt. Sandbox keys are processed strictly client-side.