Chapter 3 · Junior Developer

Scope and namespaces

Scope is *where a name is visible*. A variable created inside a function is local — it exists only while that function runs and vanishes when it returns. Code outside can't see it. Try to use it and you get NameError: name '...' is not defined.

That's the whole point: functions are sealed boxes. To get a value out, you return it and the caller catches it — you don't reach inside.

Python resolves a name with the LEGB rule, searching outward: Local (this function) → Enclosing (a function around this one) → Global (the module/file) → Built-in (len, print, …). The first match wins.

A function can *read* a global, but to *reassign* one it needs the global keyword — otherwise the assignment just creates a new local that shadows it. global works but is usually a code smell: it makes functions depend on hidden shared state. Prefer passing values in as arguments and handing results back with return.

Syntax

x = 1            # global (module-level)

def f():
    y = 2        # local — invisible outside f()
    return x + y # reads global x, local y

# LEGB lookup order: Local → Enclosing → Global → Built-in

def bump():
    global x     # needed to REASSIGN a global (a smell)
    x = x + 1

Worked examples

Local stays local — return it to get it out

def calculate(a, b):
    total = a + b      # local to calculate
    return total       # hand it back

result = calculate(5, 3)   # capture the return value
print(result)   # 8
# print(total)  → NameError: total only existed inside calculate

Reassigning a global needs the global keyword

counter = 0

def increment():
    global counter     # without this, you'd make a NEW local
    counter += 1

increment()
increment()
print(counter)   # 2

Shadowing — a local hides the global of the same name

name = "outer"

def show():
    name = "inner"   # a NEW local, doesn't touch the global
    return name

print(show(), name)   # inner outer

Common mistakes & gotchas

Using a local outside its function

A variable assigned inside a function dies when the function returns. Referencing it at module level raises NameError. The cure is almost always return the value and capture it where you call the function.

Assigning to a global without declaring it

If a function assigns to a name anywhere in its body, Python treats that name as local for the *whole* function — even before the assignment. Reading a global then assigning to it without global gives UnboundLocalError. Declare global x (or, better, pass and return).

Reaching for global as a shortcut

global makes a function silently depend on outside state, which makes bugs hard to trace and functions hard to test. It's rarely the right tool — pass values in as parameters and return results out instead.

Why it matters

Scope is what keeps a 10,000-line program from collapsing into one giant tangle of variables stepping on each other. Each function gets its own clean namespace, so you can reason about it in isolation. Understanding LEGB turns mysterious `NameError`s and `UnboundLocalError`s into a quick, obvious fix.