skip to content

What is Python's LEGB rule, and in what order does it resolve a bare name?

level: juniorimportance: must knowfreq 78%

answer

  1. Four namespaces, one fixed search order
  2. Innermost wins, search stops there
  3. Enclosing means lexically surrounding functions
  4. Global really means module-level
  5. Built-ins are consulted last of all

basics

~20 s

LEGB is the order Python searches namespaces for a bare name: Local, then any Enclosing function scopes, then the module's Global namespace, then the built-ins. The first namespace holding the name wins; if none does, Python raises NameError.

solid answer

~40 s

When Python evaluates a bare name such as `total`, it looks in four namespaces in a fixed order. **L**ocal is the current function's own namespace. **E**nclosing covers the local namespaces of any functions that textually surround this one, innermost first. **G**lobal is the module's top-level namespace — module-level, not process-wide. **B**uilt-in is the `builtins` module, which supplies `len`, `open`, `print` and the exception classes. The first scope that has the name wins, so a local `list` hides the built-in `list` for the rest of that function, and a module-level `id` hides the built-in one for that whole module. If no scope has the name, Python raises `NameError` at run time, not at import time. The scopes are decided lexically, from where the code is written, not from which function happens to be calling.

code

python · 12 lines
python
name = "global"

def outer():
    name = "enclosing"
    def inner():
        name = "local"
        return name
    return inner(), name

print(outer())          # ('local', 'enclosing')
print(name)             # global
print(min([3, 1, 2]))   # 1  -- 'min' found only in builtins

go deeper

for a junior

Be ready to say the four scopes in order and trace a short nested snippet out loud. Knowing that the search stops at the first hit, and that 'global' means module-level, covers most screening questions.

for a middle

Explain the mechanics: which layer is an array slot, which is a dictionary search, and that enclosing scopes are decided lexically at compile time rather than from the call stack.

for a senior

Show the production consequence: a name that resolves to the wrong layer fails silently until the line executes, so treat unresolved-name warnings from a linter and unexercised error branches as real risk, not noise.

for a principal

Own the convention side — module-level mutable names and casual shadowing make code that reads correctly at every call site and behaves differently per module. Decide where that is banned in your codebase and how it is enforced.

## What a "bare name" lookup actually is A bare name is an identifier used on its own — `total`, `open`, `Config` — as opposed to an attribute access like `obj.total`, which is a completely different mechanism (attribute lookup on an object). LEGB governs only bare names, and it answers one question: of all the namespaces visible here, which one supplies the binding? ## The four scopes, innermost first **Local (L).** The namespace of the function currently executing. In a function body, CPython does not use a dictionary for these at all: the compiler works out the set of local names while compiling the function and stores them in a fixed-size array on the frame, indexed by slot number. That is why local reads are the fastest lookup Python has. **Enclosing (E).** The local namespaces of functions that lexically surround this one — that is, functions whose `def` the current `def` is nested inside — searched innermost outward. A name read from an enclosing function is a *free variable*, and the compiler wires it up at compile time; this is what makes closures work. **Global (G).** The top-level namespace of the module the code was defined in. "Global" in Python is a misnomer worth correcting in an interview: it means *module-level*, not program-wide. Two modules each have their own global namespace, and a name defined at the top of one is invisible as a bare name in the other. **Built-in (B).** The namespace of the `builtins` module: `len`, `range`, `print`, `open`, `sum`, `TypeError`, `None`-adjacent helpers and so on. It is consulted last, which is exactly why you can define your own `sum` and have it win inside your module. The search stops at the first namespace that has the name. Anything further out is shadowed — not overwritten, just invisible from here. ## Lexical, not dynamic The E and G layers are chosen from the *text* of the program, not from the call stack. If `helper()` is defined in module A and called from module B, a bare name inside `helper` is resolved against module A's globals; module B's variables are never consulted. Python is statically (lexically) scoped, and there is no way to make a callee see a caller's locals by accident. This is the single most useful correction to make when a candidate says "it looks up the stack". ## What the compiler decides, and what it defers The compiler classifies each name in a function body as local, free (from an enclosing scope), or global/built-in, and emits a different instruction for each. Locals become an array-slot access. Free variables become a cell access. Anything else becomes a global-or-builtin instruction, which searches the module's global dictionary and, on a miss, the built-ins mapping — both at run time. `dis.dis` on a function shows this classification directly. One practical consequence: a mistyped global or built-in name is not caught when the module is imported. It survives until the line actually runs, then raises `NameError` with the offending name in the message. That is why a typo in a rarely-taken error branch can sit in a codebase for months. ## Where the boundaries are Two blocks behave unlike functions and are worth knowing here. A **class body** executes in its own namespace, but that namespace is *skipped* when a function defined inside the class looks a name up — methods do not get the class body as an enclosing scope. And a **module** has no enclosing scope at all: at module level, the E layer is empty and the search is simply G then B. ## Why the order matters in practice Because G precedes B, any module-level name that collides with a built-in silently replaces it for that whole module — a common source of confusing errors when someone names a variable `list`, `dict`, `id`, `type` or `open`. Because L precedes everything, the same shadow inside a single function is contained to that function. And because the search stops at the first hit, adding a name in an inner scope is a purely local change: it never edits the outer binding, it only hides it.

  • If a name is in none of the four scopes, when does Python complain?
    At run time, on the line that reads it, with `NameError: name 'x' is not defined`. The compiler classifies names but never verifies that a global or built-in name exists, so a typo inside a rarely-executed branch survives import, tests that skip that branch, and deployment — it only surfaces when the line finally runs.
  • Does a called function fall back to the calling function's local variables?
    No. Python is lexically scoped: the enclosing scopes of a function are the functions its `def` is physically nested inside, fixed at compile time. The call stack is irrelevant to name resolution. A helper defined in one module always resolves its bare names against that module's globals, whoever calls it.
  • Why is reading a local faster than reading a module-level name in CPython?
    The compiler assigns each local a slot in a fixed-size array on the frame, so a local read is an array index. A module-level or built-in name compiles to an instruction that searches the module's global dictionary and then the built-ins mapping at run time. `dis.dis` shows the two different instructions for the same-looking source line.

Looking up a word by checking, in order, the note on your desk, the notes on the desks you are sitting inside, the office noticeboard, and finally the dictionary on the shelf — you stop at the first place that has it.

saying these in an interview costs you the question

  • Says Python searches the caller's scope up the stack
  • Thinks 'global' means visible to every module
  • Puts built-ins ahead of module-level names
  • Claims a missing name is caught at import time
  • Believes an inner assignment overwrites the outer binding
  • Cannot say what the E in LEGB refers to

context