← Back to Python

LEGB Rule

PythonEntrypython

The Question

What is the LEGB rule in Python's scope resolution?

What a Strong Answer Covers

  • L-E-G-B in order
  • "nonlocal
  • "global
  • "closure

Senior-Level Answer

LEGB is Python's name resolution order: when the interpreter encounters a variable name, it searches scopes in this sequence: Local → Enclosing → Global → Built-in. The first scope where the name is found wins.

Local scope is the innermost function body currently executing. Variables assigned inside a function are local to that function by default — Python determines this at compile time, not runtime, so if a name is assigned anywhere in a function, it's treated as local throughout that function's scope (which causes the UnboundLocalError trap described below).

Enclosing scope covers any enclosing function scopes between the local function and the module level — the mechanism that makes closures work. A nested function can read variables from its enclosing function's scope. In Python 3, the `nonlocal` keyword allows a nested function to rebind (not just read) a variable in the enclosing scope.

Global scope is the module's top-level namespace. Variables defined at module level, or explicitly declared with the `global` keyword inside a function, live here. The `global` keyword tells Python to look up and bind the name in the module's global namespace instead of creating a local.

Built-in scope is the `builtins` module — containing `len`, `print`, `range`, `Exception`, etc. It's always available as the final fallback.

A common trap: the UnboundLocalError. If you read a variable before assigning it in the same function, Python has already classified it as local (because there's an assignment later in the function body) but hasn't assigned it yet at the point you read it. The fix is `global` or `nonlocal` depending on where the variable lives, or restructure to avoid the pattern.

Closures and LEGB interact importantly: a closure captures the enclosing scope's variable reference, not its value at the time of creation. The classic loop-closure bug (`lambda: i` in a for loop capturing the same `i`) stems from this. Default argument binding (`lambda i=i: i`) or `functools.partial` are the fixes.

Python's scoping is lexical (static) — determined by where code is written, not by the call stack at runtime.

What Separates a 2/3 from a 3/3

2/3 — Passing but Incomplete

Correctly names all four scopes in order, explains each with an example, and can describe what global and nonlocal do.

3/3 — Strong Answer

All of the above plus: explains the UnboundLocalError trap with the underlying cause (compile-time local classification), describes the closure variable capture semantic and the loop-lambda bug, and notes lexical vs dynamic scoping.

Common Mistakes

  • Reversing the order — candidates sometimes say Built-in is checked first, or confuse the direction of the search.
  • Thinking global/nonlocal affect lookup order — they affect assignment behavior, not where Python searches for reads.
  • Not knowing the UnboundLocalError trap — this is one of the most common Python gotchas and reveals whether LEGB knowledge is theoretical or practical.
  • Confusing Enclosing scope with Global scope — especially when there's only one level of nesting.

Follow-Up Questions

  • Why does this raise UnboundLocalError: x=1; def f(): print(x); x=2; f()? — Python classifies x as local because it's assigned in the function body, so the print reads an unassigned local, not the global x.
  • What does nonlocal do and when would you use it? — nonlocal lets a nested function rebind a variable in the enclosing (not global) scope — needed for closures that need to update a counter or accumulate state.
  • Explain why the classic for-loop lambda closure bug occurs and how to fix it. — All lambdas close over the same loop variable reference, which is the final value after the loop ends. Fix with a default argument (lambda i=i: i) to capture the value at each iteration.
  • Can you shadow a built-in name? What are the risks? — Yes — assigning list=[] shadows the built-in. It's valid Python but causes confusing errors if you later try to use the built-in. Avoided by convention.

Related Questions

  • GIL — What It Is and What It Protects
  • ThreadPoolExecutor & ProcessPoolExecutor
  • Decorators — Under the Hood
  • Generators & yield
  • Context Managers

Can You Explain This Cold?

Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.

Get Graded — Free Assessment
GrindQuestionsAITechnical interview assessment
TermsPrivacyAbout