Chapter 17 Intermediate 55 Questions

Practice Questions — Scope, LEGB Rule, and Closures

← Back to Notes
8 Easy
10 Medium
9 Hard

Topic-Specific Questions

Question 1
Easy
What is the output of the following code?
x = 10

def show():
    print(x)

show()
The function can read global variables without the global keyword.
10
Question 2
Easy
What is the output?
x = 5

def func():
    x = 10
    print(x)

func()
print(x)
Assignment inside a function creates a local variable.
10
5
Question 3
Easy
What is the output?
def func():
    y = 20
    print(y)

func()
try:
    print(y)
except NameError as e:
    print(f"Error: {e}")
Local variables do not exist outside the function.
20
Error: name 'y' is not defined
Question 4
Easy
What is the output?
x = "global"

def outer():
    x = "enclosing"
    def inner():
        print(x)
    inner()

outer()
inner() has no local x, so LEGB search finds the enclosing one.
enclosing
Question 5
Easy
What is the output?
count = 0

def increment():
    global count
    count += 1

increment()
increment()
increment()
print(count)
The global keyword allows modifying the module-level variable.
3
Question 6
Medium
What is the output?
x = 1

def func():
    x = 2
    print("Inside:", x)

func()
print("Outside:", x)
Assignment inside a function creates a NEW local variable, it does not modify the global.
Inside: 2
Outside: 1
Question 7
Medium
What is the output?
def outer():
    x = 10
    def inner():
        nonlocal x
        x += 5
    inner()
    print(x)

outer()
nonlocal allows the inner function to modify the enclosing variable.
15
Question 8
Medium
What is the output?
def multiplier(factor):
    def multiply(x):
        return x * factor
    return multiply

double = multiplier(2)
triple = multiplier(3)
print(double(5))
print(triple(5))
print(double(10))
Each call to multiplier creates a new closure with its own factor.
10
15
20
Question 9
Medium
What is the output?
x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print("inner:", x)
    inner()
    print("outer:", x)

outer()
print("global:", x)
Each scope has its own x. Assignment creates a local variable.
inner: local
outer: enclosing
global: global
Question 10
Medium
What is the output?
def make_adder(n):
    def add(x):
        return x + n
    return add

add5 = make_adder(5)
add10 = make_adder(10)

print(add5(3))
print(add10(3))
print(add5(add10(1)))
Each closure remembers its own n value.
8
13
16
Question 11
Hard
What is the output?
x = 10

def func():
    try:
        print(x)
        x = 20
    except UnboundLocalError:
        print("UnboundLocalError caught")

func()
print(x)
The assignment x = 20 makes x local for the ENTIRE function, even the print before it.
UnboundLocalError caught
10
Question 12
Hard
What is the output?
def counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    def get():
        return count
    return inc, get

inc, get = counter()
print(inc())
print(inc())
print(get())
print(inc())
print(get())
Both inc and get share the same enclosing count variable.
1
2
2
3
3
Question 13
Hard
What is the output?
funcs = []
for i in range(4):
    def f():
        return i
    funcs.append(f)

print([fn() for fn in funcs])
All closures capture the same variable i, not its value at creation time.
[3, 3, 3, 3]
Question 14
Hard
What is the output?
funcs = []
for i in range(4):
    def f(i=i):
        return i
    funcs.append(f)

print([fn() for fn in funcs])
Default parameter i=i captures the current value of i at each iteration.
[0, 1, 2, 3]
Question 15
Hard
What is the output?
a = 1
def func():
    a = 2
    def inner():
        global a
        a = 3
    inner()
    print("func:", a)

func()
print("global:", a)
inner() uses global, not nonlocal. It modifies the global a, not the enclosing a.
func: 2
global: 3
Question 16
Hard
What is the output?
def outer():
    x = 10
    def middle():
        def inner():
            nonlocal x
            x += 1
        inner()
    middle()
    return x

print(outer())
nonlocal searches upward through enclosing scopes. inner has no x, middle has no x, outer has x.
11

Mixed & Application Questions

Question 1
Easy
What is the output?
name = "Aarav"

def greet():
    print(f"Hello, {name}")

greet()
name = "Priya"
greet()
The function reads the global variable at call time, not definition time.
Hello, Aarav
Hello, Priya
Question 2
Easy
What is the output?
for i in range(5):
    pass

print(i)
Python for loops do not create a new scope. The loop variable persists.
4
Question 3
Easy
What is the output?
def func(a, b):
    c = a + b
    return c

result = func(3, 4)
print(result)
try:
    print(c)
except NameError:
    print("c is not defined")
c is a local variable inside func. It does not exist outside.
7
c is not defined
Question 4
Medium
What is the output?
def power_factory(exp):
    def power(base):
        return base ** exp
    return power

square = power_factory(2)
cube = power_factory(3)
print(square(4))
print(cube(4))
Each closure captures a different value of exp.
16
64
Question 5
Medium
What is the output?
x = [1, 2, 3]

def modify(lst):
    lst.append(4)

modify(x)
print(x)
Lists are mutable. The function modifies the same object, no global keyword needed.
[1, 2, 3, 4]
Question 6
Medium
What is the output?
x = [1, 2, 3]

def replace(lst):
    lst = [4, 5, 6]
    print("Inside:", lst)

replace(x)
print("Outside:", x)
Reassigning the parameter creates a new local reference, not modifying the original.
Inside: [4, 5, 6]
Outside: [1, 2, 3]
Question 7
Medium
What is the LEGB rule? Explain each letter.
It is the order Python searches for variable names.
LEGB stands for: Local (inside the current function), Enclosing (outer function for nested functions), Global (module level), Built-in (Python's predefined names like print, len). Python searches these scopes in this order when resolving a name.
Question 8
Medium
When do you need the global keyword and when do you need nonlocal?
Think about which scope you are trying to modify.
Use global to modify a module-level variable from inside a function. Use nonlocal to modify an enclosing function's variable from inside a nested function. Neither is needed for reading variables or for modifying mutable objects through their methods.
Question 9
Hard
What is the output?
def make_greeting(greeting):
    def greet(name):
        return f"{greeting}, {name}!"
    return greet

hello = make_greeting("Hello")
namaste = make_greeting("Namaste")

print(hello("Aarav"))
print(namaste("Priya"))
print(hello("Rohan"))
Each closure captures a different greeting value.
Hello, Aarav!
Namaste, Priya!
Hello, Rohan!
Question 10
Hard
What is the output?
print = "hello"  # Shadows built-in print
try:
    print("test")
except TypeError as e:
    del print  # Remove shadow
    print(f"Error was: {e}")
    print("Built-in restored")
Assigning to 'print' shadows the built-in. del removes the shadow.
Error was: 'str' object is not callable
Built-in restored
Question 11
Hard
What is the output?
def accumulator(start):
    total = start
    def add(n):
        nonlocal total
        total += n
        return total
    return add

acc = accumulator(100)
print(acc(10))
print(acc(20))
print(acc(30))
The closure maintains a running total across calls.
110
130
160

Multiple Choice Questions

MCQ 1
What does LEGB stand for?
  • A. List, Element, Global, Built-in
  • B. Local, Enclosing, Global, Built-in
  • C. Local, External, General, Basic
  • D. Loop, Enclosing, Global, Base
Answer: B
B is correct. LEGB stands for Local, Enclosing, Global, Built-in. This is the order Python searches for variable names.
MCQ 2
Where are local variables created?
  • A. At the module level
  • B. Inside a function
  • C. In the Python interpreter
  • D. In the import statement
Answer: B
B is correct. Local variables are created inside a function. They exist only during the function call and are destroyed when the function returns.
MCQ 3
What keyword is used to modify a global variable from inside a function?
  • A. nonlocal
  • B. extern
  • C. global
  • D. module
Answer: C
C is correct. The global keyword declares that a variable inside a function refers to the module-level global variable. nonlocal (A) is for enclosing scope, not global.
MCQ 4
Can you read a global variable inside a function without any special keyword?
  • A. No, you always need the global keyword
  • B. Yes, reading globals works without any keyword
  • C. Only if the variable is a constant
  • D. Only in Python 3.10+
Answer: B
B is correct. You can read global variables from inside a function without the global keyword. You only need global when you want to assign to (modify) the global variable.
MCQ 5
What is a closure?
  • A. A function that closes the program
  • B. A function that deletes all variables
  • C. An inner function that remembers variables from its enclosing scope
  • D. A function with no parameters
Answer: C
C is correct. A closure is a function that captures and remembers variables from the enclosing scope, even after the enclosing function has returned.
MCQ 6
What error occurs if you assign to and read a variable in the same function without declaring it global?
  • A. NameError
  • B. TypeError
  • C. UnboundLocalError
  • D. SyntaxError
Answer: C
C is correct. UnboundLocalError occurs because Python sees the assignment, marks the variable as local for the entire function, and then encounters a read before the assignment.
MCQ 7
What does the nonlocal keyword do?
  • A. Declares a global variable
  • B. Creates a new local variable
  • C. Allows a nested function to modify the enclosing function's variable
  • D. Deletes a variable from the enclosing scope
Answer: C
C is correct. nonlocal tells Python that a variable in a nested function refers to the enclosing (not global) scope's variable, allowing modification.
MCQ 8
Which scope does Python search first when looking up a variable name?
  • A. Global
  • B. Built-in
  • C. Enclosing
  • D. Local
Answer: D
D is correct. Python follows LEGB order: Local first, then Enclosing, then Global, then Built-in. It stops at the first scope where the name is found.
MCQ 9
What happens when you do list = [1, 2, 3] at module level?
  • A. Overwrites the built-in list function globally
  • B. Creates a global variable that shadows the built-in list
  • C. Raises a SyntaxError
  • D. Has no effect on the built-in
Answer: B
B is correct. A global variable named list shadows the built-in list function. The built-in still exists in the Built-in scope but is hidden. Use del list to remove the shadow.
MCQ 10
Do you need the global keyword to append to a global list?
  • A. Yes, always
  • B. No, because append modifies the existing object, not reassigns the variable
  • C. Only if the list has more than 10 elements
  • D. Only in Python 2
Answer: B
B is correct. global is needed for reassignment (my_list = new_list). Calling methods on the existing object (my_list.append(1)) does not require global because the variable itself is not being reassigned.
MCQ 11
What is the output of this code?
x = 10
def f():
    x = x + 1
    return x
f()
  • A. 11
  • B. 10
  • C. UnboundLocalError
  • D. NameError
Answer: C
C is correct. x = x + 1 contains both a read and an assignment. Python marks x as local for the entire function. The read (x + 1) happens before the assignment, causing UnboundLocalError.
MCQ 12
When Python determines variable scope, is it at compile time or runtime?
  • A. Runtime (when the line executes)
  • B. Compile time (when the function is defined)
  • C. Import time
  • D. It depends on the variable type
Answer: B
B is correct. Python determines scope at compile time (when the function definition is compiled). If an assignment to a variable exists anywhere in the function, it is treated as local for the entire function, regardless of execution flow.
MCQ 13
Why do closures in a loop often capture the same value?
  • A. Because loops in Python are buggy
  • B. Because closures capture the variable reference, not the value, and the variable changes
  • C. Because closures cannot be created in loops
  • D. Because Python optimizes loop variables
Answer: B
B is correct. Closures capture the variable, not its current value. In a loop, all closures reference the same loop variable. After the loop ends, the variable holds its final value, so all closures return that final value.
MCQ 14
How do you fix the loop-closure problem (all closures returning the same value)?
  • A. Use global inside the closure
  • B. Use a default parameter to capture the current value: def f(i=i)
  • C. Use nonlocal inside the closure
  • D. Use a while loop instead
Answer: B
B is correct. A default parameter (def f(i=i)) is evaluated at function definition time, capturing the current value of i. Each iteration creates a function with a different default, solving the shared-variable problem.
MCQ 15
What does func.__closure__ contain?
  • A. The function's source code
  • B. The function's parameters
  • C. The captured variables from the enclosing scope
  • D. The function's return value
Answer: C
C is correct. __closure__ is a tuple of cell objects containing the captured variables from the enclosing scope. Access values with func.__closure__[0].cell_contents. If the function is not a closure, __closure__ is None.
MCQ 16
Can nonlocal refer to a global variable?
  • A. Yes, nonlocal works for any outer scope
  • B. No, nonlocal only works with enclosing function scopes, not module-level globals
  • C. Only in Python 3.10+
  • D. Only if the global variable is mutable
Answer: B
B is correct. nonlocal only searches enclosing function scopes. It cannot refer to global (module-level) variables. Use global for module-level variables. If no enclosing function has the variable, nonlocal raises a SyntaxError.
MCQ 17
Which of the following is a built-in scope name?
  • A. my_variable
  • B. x
  • C. len
  • D. result
Answer: C
C is correct. len is a built-in function available in the Built-in scope. The other options are user-defined names that would be in Local or Global scope.
MCQ 18
What is the output?
x = 5
def f():
    global x
    x = 10
    def g():
        nonlocal x  # SyntaxError?
        x = 20
    g()
f()
  • A. x becomes 20
  • B. x becomes 10
  • C. SyntaxError: no binding for nonlocal 'x' found
  • D. UnboundLocalError
Answer: C
C is correct. In function f(), x is declared global, so it is not a local variable of f. Function g() uses nonlocal x, but there is no enclosing function with a local x. This causes a SyntaxError.

Coding Challenges

Challenge 1: Counter Using Closure

Easy
Write a function make_counter() that returns an increment function. Each call to the returned function should return the next integer starting from 1.
Sample Input
(No input required)
Sample Output
1 2 3
Use a closure with nonlocal. Do not use global variables.
def make_counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

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

Challenge 2: Multiplier Factory

Easy
Write a function make_multiplier(factor) that returns a function. The returned function should multiply its argument by factor. Create double (factor=2) and triple (factor=3) and test them.
Sample Input
(No input required)
Sample Output
double(5) = 10 triple(5) = 15 double(100) = 200
Use a closure. The returned function should take one argument.
def make_multiplier(factor):
    def multiply(x):
        return x * factor
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)
print(f"double(5) = {double(5)}")
print(f"triple(5) = {triple(5)}")
print(f"double(100) = {double(100)}")

Challenge 3: Logger Factory

Easy
Write a function make_logger(prefix) that returns a log function. The log function should print messages with the given prefix. Create 'ERROR' and 'INFO' loggers.
Sample Input
(No input required)
Sample Output
[ERROR] File not found [INFO] Server started [ERROR] Connection refused
Use a closure to remember the prefix.
def make_logger(prefix):
    def log(message):
        print(f"[{prefix}] {message}")
    return log

error = make_logger("ERROR")
info = make_logger("INFO")
error("File not found")
info("Server started")
error("Connection refused")

Challenge 4: Running Average Calculator

Medium
Write a function make_averager() that returns a function. Each call to the returned function with a number should return the running average of all numbers provided so far.
Sample Input
(No input required)
Sample Output
10.0 15.0 20.0 17.5
Use a closure to maintain state. Track sum and count using nonlocal.
def make_averager():
    total = 0
    count = 0
    def add(value):
        nonlocal total, count
        total += value
        count += 1
        return total / count
    return add

avg = make_averager()
print(avg(10))
print(avg(20))
print(avg(30))
print(avg(10))

Challenge 5: Fix the Loop-Closure Bug

Medium
The following code creates functions that should return 0, 1, 2, 3, 4 but all return 4. Fix it using the default parameter technique.
funcs = []
for i in range(5):
    def f():
        return i
    funcs.append(f)
print([fn() for fn in funcs])  # [4, 4, 4, 4, 4]
Sample Input
(No input required)
Sample Output
[0, 1, 2, 3, 4]
Fix using default parameter: def f(i=i).
funcs = []
for i in range(5):
    def f(i=i):
        return i
    funcs.append(f)
print([fn() for fn in funcs])

Challenge 6: Scope Tracer

Medium
Write a program that demonstrates all four LEGB scopes. Define a global variable, a function with an enclosing variable, a nested function with a local variable, and use a built-in (like len). Print which scope each variable comes from.
Sample Input
(No input required)
Sample Output
Built-in: len = <built-in function len> Global: x = global Enclosing: x = enclosing Local: x = local
Must demonstrate all four scopes: L, E, G, and B.
x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print(f"Local: x = {x}")
        print(f"Built-in: len = {len}")
    print(f"Enclosing: x = {x}")
    inner()

print(f"Global: x = {x}")
outer()

Challenge 7: Rate Limiter Closure

Hard
Write a function make_rate_limiter(max_calls) that returns a function. The returned function should track how many times it has been called. If called more than max_calls times, it should return 'Rate limit exceeded' instead of executing.
Sample Input
(No input required)
Sample Output
Call 1: OK Call 2: OK Call 3: OK Call 4: Rate limit exceeded Call 5: Rate limit exceeded
Use a closure with nonlocal to track the call count.
def make_rate_limiter(max_calls):
    calls = 0
    def check():
        nonlocal calls
        calls += 1
        if calls > max_calls:
            return "Rate limit exceeded"
        return "OK"
    return check

limiter = make_rate_limiter(3)
for i in range(1, 6):
    print(f"Call {i}: {limiter()}")

Challenge 8: Memoization Using Closure

Hard
Write a function memoize(func) that returns a new function which caches results. If the same argument is passed again, return the cached result instead of recomputing. Test with a factorial function.
Sample Input
(No input required)
Sample Output
Computing factorial(5)... 120 120 Computing factorial(3)... 6
Use a closure with a dictionary as cache. Print 'Computing...' only on first call for each argument.
def memoize(func):
    cache = {}
    def wrapper(n):
        if n not in cache:
            print(f"Computing {func.__name__}({n})...")
            cache[n] = func(n)
        return cache[n]
    return wrapper

def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)

factorial = memoize(factorial)
print(factorial(5))
print(factorial(5))
print(factorial(3))

Need to Review the Concepts?

Go back to the detailed notes for this chapter.

Read Chapter Notes

Want to learn Python with a live mentor?

Explore our Python course