Python
for loops
scoping rules
programming
Python tutorials

Scoping in Python 'for' loops

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

Python for loops do not create a new block scope, which surprises developers coming from languages with block-local loop variables. The loop variable remains available in the enclosing function or module scope after the loop finishes. Understanding this behavior is essential for closures, callbacks, and list-comprehension-related code.

Loop Variables Are in Enclosing Scope

In Python, loop assignment happens in the current scope.

python
1values = [10, 20, 30]
2
3for n in values:
4    pass
5
6print(n)  # 30

After loop completion, n still exists and holds the last iterated value. This is expected Python behavior, not a bug.

Inside a function, the same rule applies to function-local scope.

python
1
2def demo():
3    for i in range(3):
4        pass
5    return i
6
7print(demo())  # 2

Closure Gotcha in Loops

A common issue appears when lambdas or functions capture loop variables.

python
1funcs = []
2for n in [1, 2, 3]:
3    funcs.append(lambda: n)
4
5print([f() for f in funcs])  # [3, 3, 3]

All closures reference the same n, which ends as 3.

Fix by binding current value through default argument.

python
1funcs = []
2for n in [1, 2, 3]:
3    funcs.append(lambda value=n: value)
4
5print([f() for f in funcs])  # [1, 2, 3]

This pattern is especially important in async callback registration and GUI event handlers.

for Loops Versus Comprehension Scope

Python 3 list comprehensions have their own internal scope for iteration variable, unlike for statements.

python
1x = 100
2squares = [x * x for x in range(3)]
3print(squares)
4print(x)  # still 100

In this example, comprehension iteration variable does not overwrite outer x.

Generator expressions and comprehensions reduce accidental leakage compared with explicit loops when variable reuse is risky.

Scope Levels to Remember

Useful mental model:

  • local function scope,
  • enclosing function scope for nested defs,
  • global module scope,
  • built-in scope.

for statements assign into whichever of these scopes is current. They do not add a new layer by themselves.

Nested function example:

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

Default argument captures per-iteration value cleanly.

Practical Guidance for Real Code

In production code:

  • avoid reusing loop variable names in long functions,
  • use helper functions when loop logic and callback binding mix,
  • write explicit tests for closure behavior where callbacks are generated.

If readability suffers from too many inline lambdas, named functions with explicit arguments are often safer.

When output looks wrong, print both variable identity and value at definition time and execution time.

python
1callbacks = []
2for i in range(3):
3    print("define", i)
4    callbacks.append(lambda value=i: print("run", value))
5
6for cb in callbacks:
7    cb()

This quickly shows whether values were captured when defined or looked up later.

For static analysis, linters can also highlight shadowed names and suspicious loop variable reuse. Using tooling for these checks reduces subtle scoping bugs in large modules. Consistent naming conventions help even more in large teams.

Common Pitfalls

  • Assuming loop variables are block-scoped like in some other languages.
  • Creating lambdas in loops without binding current value.
  • Reusing loop variable names and overwriting meaningful outer variables.
  • Confusing loop scoping with list-comprehension scoping behavior.
  • Ignoring callback capture behavior in asynchronous code paths.

Summary

  • Python for loops assign variables in the current enclosing scope.
  • Loop variables remain available after loop completion.
  • Closure bugs in loops are fixed by binding values explicitly.
  • Python 3 list comprehensions isolate their iteration variable.
  • Clear naming and small tests prevent subtle scoping regressions.

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.