Lexical Scope and Free Variables

RoadmapsPython

Overview

Lexical scope means that a function's access to variables is determined by where the function is DEFINED in the source code, not where it is called. def outer(): rate = 1.2 def inner(): return rate # 'rate' is a free variable, not a local one return inner fn = outer() print(fn()) # 1.2 -- inner() still sees 'rate' after outer() has returned When an inner function references a variable from an outer function, that variable is called a 'free variable'. Even after the outer function finishes executing, the inner function retains access to it. Think of lexical scope like looking through a one-way mirror: the inner function can see out to the environment where it was born, but the outside cannot see in.

Lexical scope is the foundation for closures and decorators. It allows functions to carry state without relying on global variables or classes.

Where used: React-like state management, FastAPI dependency injection, Decorators

Why learn this

Code walkthrough

def outer():
  x = 10
  def inner():
    print(x)
  return inner

func = outer()
func()

Focus: print(x)

Aha moment

def create_greeters():
  name = 'Alice'
  def greeter():
    print(f'Hello, {name}')
  name = 'Bob'
  return greeter

fn = create_greeters()
fn()

Prediction: What does fn() print?

Common guess: Hello, Alice

Lexical scope binds the inner function to the variable itself, not the value it had at the exact moment the function was defined. When `fn()` runs, it looks up the current value of `name`, which is now 'Bob'.

Common mistakes

Glossary

free variable
A variable that is used inside a nested function but was defined in the surrounding, outer function, e.g. `rate` in `def discount(): return price * rate` where `rate` is defined outside.
local variable
A variable that is created inside a function and can only be seen or used within that specific function, e.g. `total = 0` defined inside `def calculate()`.

Recall questions

Understanding checks

What will this code output?

DEBUG: System started

Lexical scope dictates that the inner function accesses the variable itself, not a snapshot of its value at the time the function was defined. When `logger()` is finally called, `prefix` has been updated to 'DEBUG:'.

A junior developer believes that inner functions can freely reassign variables from an outer function just by using an equal sign (e.g., `count = 1`). Why is this incorrect?

Without the `nonlocal` keyword, assigning to a variable with the same name inside an inner function creates a new local variable in that scope instead of modifying the outer free variable.

Python assumes variables assigned inside a function are local to that function unless explicitly declared otherwise. If you want to rebind an outer variable, you must declare it as `nonlocal`.

Practice tasks

Lexical Scope and Free Variables

Currently, `multiply` relies on a global variable `factor`. Modify the code to define a function `make_multiplier(factor)` that returns an inner `multiply` function, so that `factor` is a free variable enclosed by lexical scope.

Challenge

Stateful Counter

Create a function `make_counter` that takes a `start` value. It should return an inner function that, when called without arguments, increments the `start` value by 1 and returns the new value. You will need to modify a free variable.

Continue learning

Previous: LEGB rule: variable scope in Python

Previous: Defining functions and return values

Previous: First-class functions: passing and returning functions

Next: Closures

Next: Closure Mechanics

Return to Python Roadmap