Python
nested functions
variable scoping
duplicate
programming

Python nested functions variable scoping

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Nested functions in Python are straightforward when the inner function only reads values from the outer scope. Confusion starts when the inner function tries to assign to those names, because assignment changes how Python resolves scope. The fix is to understand the LEGB lookup rule and to use nonlocal or global only when you truly mean to rebind a name outside the current function.

The LEGB Rule

Python resolves names in this order:

  • Local
  • Enclosing
  • Global
  • Built-in

That means an inner function can read variables from its enclosing function, but writing to them is different. If you assign to a name inside a function, Python treats that name as local to that function unless you declare otherwise.

Reading an Outer Variable

Reading works naturally:

python
1def outer():
2    message = "hello"
3
4    def inner():
5        print(message)
6
7    inner()
8
9
10outer()

inner can access message because it lives in the enclosing scope.

Why Assignment Causes Problems

This version fails:

python
1def outer():
2    count = 0
3
4    def inner():
5        count += 1
6        print(count)
7
8    inner()
9
10
11outer()

Python raises UnboundLocalError. The reason is not that count is invisible. The problem is that count += 1 counts as assignment, so Python marks count as local to inner, then notices it is being read before that local value exists.

Use nonlocal for Enclosing Function Variables

If the inner function should modify the variable from the enclosing function, declare it nonlocal:

python
1def outer():
2    count = 0
3
4    def inner():
5        nonlocal count
6        count += 1
7        print(count)
8
9    inner()
10    inner()
11
12
13outer()

Now both calls update the same count stored in the enclosing scope.

Use nonlocal only for names defined in an enclosing function. It does not refer to module-level globals.

Use global for Module-Level Names

If the name lives at module scope and you want to rebind it, use global:

python
1total = 0
2
3
4def add_one():
5    global total
6    total += 1
7
8
9add_one()
10print(total)

This works, but global state is usually harder to test and reason about than enclosed state.

Closures and Remembered State

Nested functions become especially useful when you return the inner function and let it keep access to the outer variables. That is called a closure.

python
1def make_counter():
2    count = 0
3
4    def increment():
5        nonlocal count
6        count += 1
7        return count
8
9    return increment
10
11
12counter = make_counter()
13print(counter())
14print(counter())

The returned function remembers count even after make_counter has finished.

A Common Late-Binding Surprise

Another scoping issue appears when nested functions are created in a loop:

python
1funcs = []
2
3for i in range(3):
4    def show():
5        return i
6    funcs.append(show)
7
8print([f() for f in funcs])

This prints 2, 2, 2, not 0, 1, 2. Each function looks up i when it is called, not when it is defined.

One common fix is to bind the current value as a default argument:

python
1funcs = []
2
3for i in range(3):
4    def show(i=i):
5        return i
6    funcs.append(show)
7
8print([f() for f in funcs])

Now the result is 0, 1, 2.

When to Prefer Another Design

If nested functions start carrying a large amount of mutable state, a small class or dataclass may be clearer. Closures are excellent for compact stateful helpers, but they are not always the best long-term shape for complicated logic.

Common Pitfalls

  • Assuming reading and writing outer variables follow the same scoping rule.
  • Forgetting that assignment makes a name local unless nonlocal or global is declared.
  • Using global when the correct target is an enclosing function variable.
  • Hitting late binding in loops and thinking closures copied the loop variable automatically.
  • Building complex mutable state in closures when a small object would be clearer.

Summary

  • Python resolves names with the LEGB rule.
  • Inner functions can read enclosing variables directly.
  • To rebind an enclosing function variable, use nonlocal.
  • To rebind a module-level variable, use global.
  • Closures are useful, but assignment and late binding are the two main sources of confusion.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.