1. What happens when a mutable object is used as a default parameter in a Python function?
def add_item(item, target=[]):
target.append(item)
return target
print(add_item(1))
print(add_item(2))
Output: [1] followed by [1, 2]
Default arguments in Python are evaluated exactly once — at function definition time, not on every call. The [] you write in the signature is a single list object, created when the def statement runs, and that same object is reused for every subsequent call that doesn’t pass a target.
So the first call add_item(1) appends to the default list, which now holds [1]. The second call add_item(2) uses the same list — still holding the 1 from before — and appends 2. Output: [1] then [1, 2].
This is the famous mutable-default-argument bug. The state “leaks” between calls, which is almost never what you want.
The standard fix is to default to None and create a fresh list inside the function:
def add_item(item, target=None):
if target is None:
target = []
target.append(item)
return target
Now every call that omits target gets a brand-new list. The interview takeaway: defaults are evaluated once; never use a mutable literal as a default.
Answer:
[1] followed by [1, 2]
Default arguments in Python are evaluated exactly once — at function definition time, not on every call. The [] you write in the signature is a single list object, created when the def statement runs, and that same object is reused for every subsequent call that doesn’t pass a target.
So the first call add_item(1) appends to the default list, which now holds [1]. The second call add_item(2) uses the same list — still holding the 1 from before — and appends 2. Output: [1] then [1, 2].
This is the famous mutable-default-argument bug. The state “leaks” between calls, which is almost never what you want.
The standard fix is to default to None and create a fresh list inside the function:
def add_item(item, target=None):
if target is None:
target = []
target.append(item)
return target
Now every call that omits target gets a brand-new list. The interview takeaway: defaults are evaluated once; never use a mutable literal as a default.
2. What is the scope resolution order that Python follows for variable lookups?
Answer: LEGB — Local → Enclosing → Global → Built-in.
When Python needs to resolve a name, it searches the scopes in a fixed order:
- Local — the current function’s namespace.
- Enclosing — the namespaces of outer functions that wrap the current one (for nested functions/closures).
- Global — the module level.
- Built-in — the builtin namespace (
len,print,range, etc.).
The search stops at the first scope that contains the name. If none does, you get a NameError.
A subtle point worth knowing: assignment changes scope. If a function assigns to a variable, that variable is local to the function (unless declared global or nonlocal) — even if an outer scope has the same name. That’s why a line like print(x); x = 1 raises UnboundLocalError when x is later assigned: Python sees the assignment and marks x local for the whole function, so the print finds no local x yet.
The interview answer is simply: LEGB, in that exact order.
Answer:
LEGB — Local → Enclosing → Global → Built-in.
When Python needs to resolve a name, it searches the scopes in a fixed order:
- Local — the current function’s namespace.
- Enclosing — the namespaces of outer functions that wrap the current one (for nested functions/closures).
- Global — the module level.
- Built-in — the builtin namespace (
len,print,range, etc.).
The search stops at the first scope that contains the name. If none does, you get a NameError.
A subtle point worth knowing: assignment changes scope. If a function assigns to a variable, that variable is local to the function (unless declared global or nonlocal) — even if an outer scope has the same name. That’s why a line like print(x); x = 1 raises UnboundLocalError when x is later assigned: Python sees the assignment and marks x local for the whole function, so the print finds no local x yet.
The interview answer is simply: LEGB, in that exact order.
3. What is the correct keyword used to modify an outer non-global variable inside a nested function?
Answer: nonlocal.
Python’s scoping rules make a subtle but crucial distinction. Inside a nested function, you can read a variable from an enclosing function without any declaration. But if you try to assign to it, Python assumes you’re creating a new local variable — unless you say otherwise.
globaldeclares that a name refers to the module-level (global) scope.nonlocaldeclares that a name refers to a variable in the nearest enclosing, non-global scope — i.e., an outer function’s local variable.
Example:
def outer():
x = 10
def inner():
nonlocal x
x = 20
inner()
print(x) # 20 — changed by inner
Without nonlocal, x = 20 inside inner would create a brand-new local x, leaving the outer x untouched.
The interview answer: nonlocal is the keyword for binding to an enclosing (but not global) scope.
Answer:
nonlocal.
Python’s scoping rules make a subtle but crucial distinction. Inside a nested function, you can read a variable from an enclosing function without any declaration. But if you try to assign to it, Python assumes you’re creating a new local variable — unless you say otherwise.
globaldeclares that a name refers to the module-level (global) scope.nonlocaldeclares that a name refers to a variable in the nearest enclosing, non-global scope — i.e., an outer function’s local variable.
Example:
def outer():
x = 10
def inner():
nonlocal x
x = 20
inner()
print(x) # 20 — changed by inner
Without nonlocal, x = 20 inside inner would create a brand-new local x, leaving the outer x untouched.
The interview answer: nonlocal is the keyword for binding to an enclosing (but not global) scope.
4. What happens if you modify a global variable inside a function without the global declaration?
Answer: If you assign to the name, Python creates a new local variable; if you reference it before assigning, you get UnboundLocalError.
Python decides a variable’s scope by where it’s assigned, not where it’s used. If a function contains any assignment to a name, that name is treated as local for the entire function — even lines before the assignment.
Two cases follow:
- Assignment with no prior reference:
x = 5inside the function creates a localx. The globalxis untouched. No error — but no global modification either. - Reference before assignment: if the function does
print(x)and laterx = 5, Python has already markedxas local. Theprintruns before the local exists, so it raisesUnboundLocalError— notNameError, even though a globalxexists.
To actually modify the global, you must declare global x at the top of the function. The interview answer: assignment makes it local (or raises UnboundLocalError when referenced before assignment); without global, the global stays untouched.
Answer:
If you assign to the name, Python creates a new local variable; if you reference it before assigning, you get UnboundLocalError.
Python decides a variable’s scope by where it’s assigned, not where it’s used. If a function contains any assignment to a name, that name is treated as local for the entire function — even lines before the assignment.
Two cases follow:
- Assignment with no prior reference:
x = 5inside the function creates a localx. The globalxis untouched. No error — but no global modification either. - Reference before assignment: if the function does
print(x)and laterx = 5, Python has already markedxas local. Theprintruns before the local exists, so it raisesUnboundLocalError— notNameError, even though a globalxexists.
To actually modify the global, you must declare global x at the top of the function. The interview answer: assignment makes it local (or raises UnboundLocalError when referenced before assignment); without global, the global stays untouched.
5. What is the default return value of a Python function that executes without an explicit return statement?
Answer: None.
If a function’s body runs to the end without hitting a return (or a return with no value), Python implicitly returns None.
def f():
x = 1
print(f()) # None
Every Python function returns something — there’s no “void” concept like in C/Java. Functions that conceptually return nothing actually return the None object. This is why checking the result of a mutating method (list.append returns None) is a classic bug: the value is silently discarded.
The interview answer: None — implicit, always.
Answer:
None.
If a function’s body runs to the end without hitting a return (or a return with no value), Python implicitly returns None.
def f():
x = 1
print(f()) # None
Every Python function returns something — there’s no “void” concept like in C/Java. Functions that conceptually return nothing actually return the None object. This is why checking the result of a mutating method (list.append returns None) is a classic bug: the value is silently discarded.
The interview answer: None — implicit, always.
Premium Content
Unlock Functions & Scope and all premium lessons with a subscription.
From ₹199.99/year — See plans