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.
  • return value — 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.

Loading diagram...

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_totaltotal, count, and average are bound by assignment inside the body, so they are local; the dict returned is the only way the caller sees their values.
  • add_to_listitem and initial_value are local because they are parameters; local_list is local because it is assigned inside the body. Each call rebuilds local_list from scratch.
  • The commented print(total) — uncommenting it raises NameError: 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

  1. Do return the locals you needreturn is the only way data survives once the call frame is destroyed.
  2. Do treat each call as independent — assume nothing carries over between calls; if it must, pass it in or return it out.
  3. Do read NameError as a scope clue — when one fires, ask first whether the name was only ever bound inside a function you've already left.

Don'ts

  1. Don't reach for global to "share" a local across functions — it couples unrelated callers and erases the per-call independence locals give you.
  2. 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.
  3. 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.