Consider this pseudocode: x = 10 is defined globally, then a function outer() defines x = 20 and calls an inner function inner() which prints x. inner() prints 20, not 10. Which principle explains this?
ADynamic scoping — x is resolved at runtime by scanning the call stack for the most recent assignment
BLexical (static) scoping — the compiler searches outward from the innermost enclosing scope; outer's x = 20 is found before the global x = 10
CFlat symbol tables — only one x can exist per program, so the most recently written value is used
DForward reference resolution — the compiler always uses the last assignment to x anywhere in the program
In lexical scoping, name lookup starts at the immediately enclosing scope and walks outward. When inner() looks up x, it finds outer()'s x = 20 before reaching the global scope — so 20 is printed. This is static because the lookup path is determined entirely by the textual structure of the program, not by how it runs. Dynamic scoping would also print 20 here (since outer() called inner()), but the reason is different — dynamic scoping follows the call stack, not textual nesting, and gives different results in other call patterns.
Question 2 Multiple Choice
A language allows two mutually recursive functions — A calls B, and B calls A — declared in the same scope, with A defined before B. What special handling does this require during scope resolution?
ANone — a single forward pass handles all name uses automatically
BEither a two-pass approach or a declare-then-define protocol, because when A is being processed, B has not yet been declared and the forward reference cannot be resolved in one pass
CDynamic scoping, because the mutual call order cannot be determined at compile time
DFlat symbol tables, so both functions share a global namespace and see each other immediately
When function A is processed and contains a call to B, B has not yet appeared in the source. In a single-pass compiler, B is simply unknown at that point. Solutions include: (1) a two-pass approach where the first pass registers all top-level declarations and the second resolves uses; or (2) a declare-then-define protocol where the compiler accepts a forward declaration of B's signature before seeing its body. Neither flat tables nor dynamic scoping solves the problem — they change where names are stored or when resolution happens, but the forward reference still needs to be handled explicitly.
Question 3 True / False
In a language with lexical scoping, declaring a variable with the same name as an outer-scope variable inside an inner scope causes the inner declaration to shadow the outer one during name lookup.
TTrue
FFalse
Answer: True
Shadowing is a direct consequence of how scope stacks work. When the compiler looks up a name, it starts at the innermost (most recently pushed) scope and walks outward. The first match wins. An inner declaration with the same name is found before the outer one, so the outer variable is inaccessible by that name within the inner scope. This is intentional — it allows inner scopes to introduce local bindings without breaking outer code — but it can cause subtle bugs when the shadowing is accidental.
Question 4 True / False
After scope resolution is complete, later compiler phases such as type checking and code generation is expected to re-perform name lookups to ensure they reference the correct declarations.
TTrue
FFalse
Answer: False
The purpose of scope resolution is precisely to eliminate repeated lookups. Each name-use node in the AST is annotated with a direct pointer to its resolved declaration during the resolution phase. Later phases — type checking, optimization, code generation — follow these stored pointers rather than re-walking the scope hierarchy. This makes subsequent phases simpler, faster, and correct by construction: the resolved binding is an immutable fact recorded in the AST. Re-doing lookups would be redundant and could introduce inconsistencies if the scope structure changed between phases.
Question 5 Short Answer
What is the difference between lexical (static) scoping and dynamic scoping, and why does the distinction matter for compiler design?
Think about your answer, then reveal below.
Model answer: In lexical scoping, a name's binding is determined by its textual position in the source code — the compiler searches outward through the textually enclosing scopes at compile time. In dynamic scoping, a name is resolved by searching the call stack at runtime — the most recently active binding in the call chain wins. Lexical scoping can be fully resolved at compile time, allowing the compiler to annotate each name-use with a static pointer to its declaration; code generation is straightforward. Dynamic scoping requires a runtime lookup mechanism (often a global association list or per-thread stack), which is slower and harder to reason about. Most modern languages (Python, Java, C, Haskell) use lexical scoping; dynamic scoping survives in some Lisps (for special variables) and shell scripts.
The compiler design implication is significant: lexical scoping lets the compiler 'freeze' all name bindings into the AST before generating code, so downstream phases never need name lookups. Dynamic scoping defers this work to runtime, which means code generation must emit lookup instructions, and the type checker cannot statically verify which declaration a name refers to without whole-program analysis.