Scope determines where in your code a variable can be seen and used. Python resolves this with a simple rule known as LEGB — Local, Enclosing, Global, Built-in — meaning it looks for a name first inside the current function, then in any enclosing function, then at the module level, and finally among Python's built-ins, stopping at the first match it finds.

Local vs. Global Scope

count = 0  # global variable

def increment():
    count = count + 1  # ERROR
    return count

increment()

Output:

UnboundLocalError: cannot access local variable 'count' where it is not associated with a value

This surprises a lot of beginners: Python sees the assignment count = count + 1 inside the function and decides count is a local variable for the entire function body — so when it tries to read count on the right-hand side before it's been locally assigned, it fails. Simply reading a global variable works fine; the problem only appears once you try to assign to a name that shares a global variable's name.

The global Keyword

count = 0

def increment():
    global count
    count = count + 1
    return count

print(increment())
print(increment())
print(count)

Output:

1
2
2

Declaring global count tells Python explicitly: "don't create a new local variable — modify the module-level one." Use this sparingly; functions that quietly mutate global state are harder to reason about than ones that take input and return output.

The nonlocal Keyword in Nested Functions

def make_counter():
    count = 0  # enclosing scope

    def increment():
        nonlocal count
        count += 1
        return count

    return increment

counter = make_counter()
print(counter())
print(counter())
print(counter())

Output:

1
2
3

nonlocal is the equivalent of global, but for an enclosing function's scope rather than the module's global scope — it's what makes closures like make_counter() able to remember and update state between calls. This pattern is also central to how decorators work under the hood.