Free lesson
Understand scope
You will understand variable scope in Python. Recognize local variables inside functions, understand when to use the global keyword, and avoid common scope-related bugs.
~25 min read · Free to read — no subscription required.
Recognizing Local Variables
Local variables are the building blocks of function isolation in Python — recognizing them is the foundation for writing predictable, reusable functions.
Introduction
When you write a function and reference a name inside it, Python decides whether that name is local, enclosing, global, or built-in — and if it's local, you cannot reach it from outside the function. If you've ever stared at a confusing NameError after a function returns, or watched two test cases corrupt each other through what looked like "shared" state, the root cause is almost always a misread of local scope. By the end of this lesson you'll be able to identify which variables in a function are local, explain why each call gets its own fresh copy, and return the data you need so callers can keep using it.
Key Terminology
- local variable — A name created inside a function body (by assignment or as a parameter) that exists only while that call is running. Recognizing locals tells you which names will disappear the moment the function returns.
- scope — The region of code where a name is visible. Local scope is the smallest unit; understanding it is how you predict what
print(x)can and cannot reach. - NameError — The exception Python raises when you reference a name that is not bound in any reachable scope. This is how local-variable mistakes almost always surface at runtime.
- call frame — The fresh execution context Python creates each time a function runs. Every frame holds its own copy of the function's local variables, which is why recursive and repeated calls don't clobber each other.
returnvalue — The object a function hands back to its caller; the only way local data survives after its call frame is destroyed.
Concepts
Python resolves every name reference using the LEGB rule: Local first, then Enclosing, then Global, then Built-in. A name becomes local the moment it is bound inside a function — by assignment, by appearing in the parameter list, or by being introduced via for, with, or except.
What makes a variable local
Variables defined inside a function are local to that function. They exist only while the function is running and cannot be accessed from outside. Trying to read them from outside raises NameError. The only way to keep a local value alive past the call is to return it (see Code Walkthrough).
Each call gets its own frame
Every invocation creates a fresh call frame with its own set of locals. Two calls to the same function — even back-to-back — do not share variables. This is what makes functions safely reusable: one call cannot corrupt another's state.
Returning is the escape hatch
Because locals die with the frame, any value you want to keep must leave through return. Recognizing which names in a function are local tells you exactly which ones need to appear in the return statement if the caller is going to use them.
Code Walkthrough
The snippet below demonstrates all three concepts at once: total, count, and average are local to calculate_total; local_list shows that each call to add_to_list builds an independent frame; and both functions return their locals so callers can keep the data.
Code snippetpython
1def calculate_total(items): 2 """Locals total, count, average exist only during this call.""" 3 total = 0 4 count = 0 5 for item in items: 6 total += item 7 count += 1 8 average = total / count if count > 0 else 0 9 return {"total": total, "count": count, "average": average} 10 11def add_to_list(item, initial_value=0): 12 """Each call gets a fresh local_list — no shared state.""" 13 local_list = [] 14 local_list.append(initial_value) 15 local_list.append(item) 16 return local_list 17 18result = calculate_total([10, 20, 30]) 19print(result) # {'total': 60, 'count': 3, 'average': 20.0} 20 21# print(total) # NameError — total is local to calculate_total 22 23print(add_to_list("A")) # [0, 'A'] 24print(add_to_list("B")) # [0, 'B'] 25print(add_to_list("C", 100)) # [100, 'C']
calculate_total—total,count, andaverageare bound by assignment inside the body, so they are local; the dict returned is the only way the caller sees their values.add_to_list—itemandinitial_valueare local because they are parameters;local_listis local because it is assigned inside the body. Each call rebuildslocal_listfrom scratch.- The commented
print(total)— uncommenting it raisesNameError: name 'total' is not defined, which is the runtime signal that you tried to read a local from outside its scope.
You'll know it works when running the script prints {'total': 60, 'count': 3, 'average': 20.0} followed by three independent lists, and you can predict — before running it — that uncommenting print(total) would raise NameError.
Do's and Don'ts
Do's
- ✓Do return the locals you need —
returnis the only way data survives once the call frame is destroyed. - ✓Do treat each call as independent — assume nothing carries over between calls; if it must, pass it in or
returnit out. - ✓Do read
NameErroras a scope clue — when one fires, ask first whether the name was only ever bound inside a function you've already left.
Don'ts
- ✗Don't reach for
globalto "share" a local across functions — it couples unrelated callers and erases the per-call independence locals give you. - ✗Don't assume two calls share state — each invocation gets its own frame, so don't rely on the previous call's locals being there.
- ✗Don't try to read a function's locals from outside — they don't exist after the call returns; restructure to return what you need instead.
Keep going with GenAI Agent Engineering
Create a free account to track your progress and open this lesson in the full learning view. Subscribe to unlock the entire path — every goal, the hands-on labs, quizzes, and your verifiable skill graph — from . Cancel anytime.