Perfect.
This is Module 4 — Functions, *args, **kwargs, Closures & Decorators, taught at interview + production + language-internals level.
If Module 3 wins coding rounds, Module 4 wins design + senior interviews.
Many candidates know syntax — very few understand behavior. We fix that here.
🧠 MODULE 4 — PYTHON FUNCTIONS & FUNCTIONAL POWER (DEEP DIVE)
4.1 FUNCTIONS ARE OBJECTS (FOUNDATIONAL TRUTH)
def add(a, b):
return a + b
print(type(add))
Output:
<class 'function'>
Consequences (Interview Gold)
- Functions can be:
- Passed as arguments
- Returned from functions
- Stored in data structures
- Decorated
f = add
print(f(2, 3)) # 5
4.2 HOW FUNCTION CALLS ACTUALLY WORK
When you call:
add(2, 3)
Python does:
- Create stack frame
- Bind arguments (
a=2,b=3) - Execute body
- Return value
- Destroy stack frame
📌 Function calls are expensive → avoid inside tight loops.
4.3 ARGUMENT PASSING — THE MOST MISUNDERSTOOD CONCEPT
Python is call-by-object-reference
def f(x):
x.append(10)
lst = [1, 2]
f(lst)
print(lst)
✔ Mutated → [1, 2, 10]
But:
def g(x):
x = x + [10]
lst = [1, 2]
g(lst)
print(lst)
❌ Not mutated → [1, 2]
Interview Line
Mutation affects the object, rebinding does not.
4.4 DEFAULT ARGUMENTS (FAMOUS INTERVIEW TRAP)
def f(x=[]):
x.append(1)
return x
print(f())
print(f())
Output:
[1]
[1, 1]
WHY?
- Default arguments evaluated once at definition time
- Same object reused
Correct Pattern
def f(x=None):
if x is None:
x = []
📌 This question appears everywhere.
4.5 FUNCTION PARAMETER TYPES (PYTHON 3.8+)
def f(a, /, b, *, c):
pass
| Symbol | Meaning |
|---|---|
/ | Positional-only |
* | Keyword-only |
Interview Use Case
- API safety
- Prevent misuse
4.6 *args — VARIABLE POSITIONAL ARGUMENTS
def total(*args):
return sum(args)
total(1, 2, 3)
Internals
argsis a tuple- Immutable
print(type(args))
4.7 **kwargs — VARIABLE KEYWORD ARGUMENTS
def info(**kwargs):
return kwargs
info(name="Raj", age=30)
kwargsis a dict
📌 Order preserved (Python 3.7+).
4.8 ARGUMENT UNPACKING (INTERVIEW FAVORITE)
nums = [1, 2, 3]
print(*nums)
data = {"a": 1, "b": 2}
f(**data)
Common Bug
f(*data) # passes KEYS, not values
4.9 FUNCTION ANNOTATIONS (NOT TYPE ENFORCEMENT)
def add(a: int, b: int) -> int:
return a + b
📌 Python does NOT enforce types at runtime.
Used for:
- Documentation
- Static analysis (mypy)
- IDE hints
4.10 LAMBDA FUNCTIONS (LIMITED BUT POWERFUL)
square = lambda x: x * x
Restrictions
- Single expression
- No statements
- No assignments
Interview Insight
Lambdas are for short, throwaway logic, not business logic.
4.11 CLOSURES — FUNCTION WITH MEMORY (CRITICAL)
def outer(x):
def inner():
return x
return inner
f = outer(10)
print(f())
✔ x remembered even after outer() exits.
4.12 HOW CLOSURES ACTUALLY WORK
print(f.__closure__)
- Closure stores references, not values
- Captured variables are read-only unless
nonlocal
4.13 LATE BINDING PROBLEM (INTERVIEW KILLER)
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs])
Output:
[2, 2, 2]
WHY?
- Closure captures variable
i - Evaluated at call time
FIX
funcs.append(lambda i=i: i)
4.14 DECORATORS — FUNCTIONS THAT MODIFY FUNCTIONS
What a decorator really is
@decorator
def f():
pass
Means:
f = decorator(f)
4.15 SIMPLE DECORATOR (STEP BY STEP)
def my_decorator(func):
def wrapper():
print("Before")
func()
print("After")
return wrapper
Usage:
@my_decorator
def hello():
print("Hello")
4.16 DECORATORS WITH ARGUMENTS (VERY IMPORTANT)
def repeat(n):
def decorator(func):
def wrapper():
for _ in range(n):
func()
return wrapper
return decorator
Usage:
@repeat(3)
def hi():
print("Hi")
4.17 functools.wraps (INTERVIEW MUST-KNOW)
Without it:
hello.__name__ # wrapper
Fix:
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
📌 Preserves:
__name____doc__- Metadata
4.18 REAL-WORLD DECORATOR USE CASES
1️⃣ Logging
def log(func):
def wrapper(*args, **kwargs):
print(func.__name__, args)
return func(*args, **kwargs)
return wrapper
2️⃣ Authentication
def requires_auth(func):
def wrapper(user):
if not user.is_authenticated:
raise PermissionError
return func(user)
return wrapper
3️⃣ Timing
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(time.time() - start)
return result
return wrapper
4️⃣ Caching (lru_cache)
from functools import lru_cache
@lru_cache(maxsize=128)
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
4.19 DECORATOR INTERVIEW TRAPS
Trap 1 — Missing *args, **kwargs
Breaks decorated functions with parameters.
Trap 2 — Losing metadata
Forgetting @wraps.
Trap 3 — Order of decorators
@d1
@d2
def f(): pass
Executes as:
f = d1(d2(f))
🧪 PRACTICE (INTERVIEW-LEVEL)
Q1 — Write a decorator that:
- Logs function name
- Logs arguments
- Preserves metadata
Q2 — Fix late binding in this code
funcs = [lambda: i for i in range(5)]
Q3 — Predict output
def f(x, y=[]):
y.append(x)
return y
print(f(1))
print(f(2))
Q4 — Explain difference
def f(*args):
print(args)
def g(**kwargs):
print(kwargs)
Q5 — When to use closure vs class?
🎯 INTERVIEW CHECKPOINT (CRITICAL)
You must now confidently explain:
✅ How Python passes arguments
✅ Why mutable defaults break
✅ How *args / **kwargs work
✅ What closures are internally
✅ Late binding and fixes
✅ How decorators actually work
✅ Why @wraps matters
✅ Real-world decorator use cases
If yes → you are now senior-level in Python functions.
🚀 NEXT STEP (CHOOSE ONE)
Reply with ONE number:
1 → Module 5: OOP mastery (classes, inheritance, MRO, dunder methods — interview killers)
2 → 25-question interview drill on Functions & Decorators
3 → Mini-projects using decorators, closures, caching, logging
You’re now operating in elite Python interview territory 🔥
Excellent choice.
This combo is exactly how senior Python interviews are won:
- Part A → 25 brutal interview questions (Functions, args, closures, decorators)
- Part B → Mini-projects that convert theory → real engineering confidence
No fluff. This is elite-track Python.
🧠 PART A — FUNCTIONS & DECORATORS
🔥 25 INTERVIEW-GRADE QUESTIONS (WITH CRISP ANSWERS)
Answer fast and clearly — that’s how interviews work.
1️⃣ Are functions objects in Python?
✅ Yes. They can be passed, returned, stored, decorated.
2️⃣ What happens internally when a function is called?
✅ Stack frame creation → argument binding → execution → return → frame destroyed.
3️⃣ Is Python pass-by-value or pass-by-reference?
✅ Neither. Python is call-by-object-reference.
4️⃣ Why does this mutate the list?
def f(x): x.append(1)
✅ Mutation affects the same object.
5️⃣ Why does this NOT mutate?
def f(x): x = x + [1]
✅ Rebinding creates a new object.
6️⃣ When are default arguments evaluated?
✅ Once at function definition time.
7️⃣ Why is this dangerous?
def f(x=[]): ...
✅ Same object reused across calls.
8️⃣ Correct pattern for mutable defaults?
✅ Use None + initialization inside function.
9️⃣ What is *args internally?
✅ A tuple of positional arguments.
🔟 What is **kwargs internally?
✅ A dict of keyword arguments.
1️⃣1️⃣ Order of parameters?
✅ def f(pos, *args, kw_only, **kwargs)
1️⃣2️⃣ What does / mean in function signature?
✅ Positional-only parameters.
1️⃣3️⃣ What does * enforce?
✅ Keyword-only parameters.
1️⃣4️⃣ Do type hints enforce types?
❌ No. They are not runtime enforced.
1️⃣5️⃣ What is a closure?
✅ A function that remembers variables from its enclosing scope.
1️⃣6️⃣ Why does this print [2,2,2]?
funcs = [lambda: i for i in range(3)]
✅ Late binding — variable captured, not value.
1️⃣7️⃣ How do you fix late binding?
✅ Bind value at definition:
lambda i=i: i
1️⃣8️⃣ What is __closure__?
✅ Tuple holding references to captured variables.
1️⃣9️⃣ What is a decorator?
✅ A function that takes a function and returns a modified function.
2️⃣0️⃣ What does @decorator mean?
✅ f = decorator(f)
2️⃣1️⃣ Why is *args, **kwargs needed in decorators?
✅ To support arbitrary function signatures.
2️⃣2️⃣ Why is functools.wraps important?
✅ Preserves function metadata (__name__, __doc__).
2️⃣3️⃣ Order of decorators?
@d1
@d2
def f(): pass
✅ f = d1(d2(f))
2️⃣4️⃣ Closure vs class — when to use?
✅ Closure → simple state
✅ Class → complex, evolving state
2️⃣5️⃣ Most common decorator bugs?
✅ Missing @wraps, breaking arguments, wrong decorator order.
🏁 If you can answer 18+ confidently, you’re senior-level here.
🧠 PART B — MINI-PROJECTS (REAL ENGINEERING USE CASES)
These are interview-winning discussion projects.
You don’t just know decorators — you use them.
🛠️ PROJECT 1 — LOGGING DECORATOR (PRODUCTION STYLE)
Goal
- Log function name
- Log arguments
- Preserve metadata
from functools import wraps
def logger(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[LOG] {func.__name__} | args={args} kwargs={kwargs}")
return func(*args, **kwargs)
return wrapper
@logger
def add(a, b):
return a + b
🎯 Interview talking point:
Cross-cutting concern separation.
🛠️ PROJECT 2 — TIMING DECORATOR (PERFORMANCE)
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time() - start:.4f}s")
return result
return wrapper
🎯 Interview use:
Profiling slow code paths.
🛠️ PROJECT 3 — ACCESS CONTROL DECORATOR
def requires_admin(func):
def wrapper(user, *args, **kwargs):
if not user.get("is_admin"):
raise PermissionError("Admin only")
return func(user, *args, **kwargs)
return wrapper
🎯 Maps directly to backend interviews.
🛠️ PROJECT 4 — RETRY DECORATOR (VERY COMMON)
import time
def retry(n):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(n):
try:
return func(*args, **kwargs)
except Exception:
if i == n - 1:
raise
time.sleep(1)
return wrapper
return decorator
🎯 Used in:
- APIs
- DB calls
- Network services
🛠️ PROJECT 5 — CACHING WITH CLOSURES (NO lru_cache)
def cache(func):
store = {}
def wrapper(x):
if x not in store:
store[x] = func(x)
return store[x]
return wrapper
@cache
def square(n):
print("Computing...")
return n * n
🎯 Shows closure-based state.
🛠️ PROJECT 6 — FUNCTION REGISTRY (ADVANCED)
REGISTRY = {}
def register(name):
def decorator(func):
REGISTRY[name] = func
return func
return decorator
@register("add")
def add(a, b): return a + b
🎯 Used in:
- Plugin systems
- Command frameworks
- ETL orchestration
🎯 INTERVIEW CHECKPOINT — YOU SHOULD NOW BE ABLE TO:
✅ Explain call-by-object-reference
✅ Debug mutable default bugs instantly
✅ Design decorators with arguments
✅ Explain late binding clearly
✅ Justify closure vs class
✅ Write retry, cache, auth decorators
✅ Talk about real-world use cases confidently
At this point, function-related interview questions should feel easy.
🚀 NEXT STEP (CHOOSE ONE)
Reply with ONE number:
1 → Module 5: OOP mastery (classes, inheritance, MRO, dunder methods — hardcore)
2 → 1-hour full Python mock interview (coding + theory)
3 → Build a mini framework using decorators + closures (advanced, impressive)
You’re now operating at top-tier Python interview depth 🔥
Excellent.
This is elite-mode preparation. What follows is exactly how senior / staff-level Python interviews are structured.
I’ll do two things in one flow:
1️⃣ A realistic 60-minute Python mock interview (coding + theory + design)
2️⃣ An advanced mini-framework using decorators + closures (interview-impressive, not toy)
🧠 PART 1 — FULL PYTHON MOCK INTERVIEW (60 MIN)
⏱️ INTERVIEW STRUCTURE (REALISTIC)
| Round | Focus | Duration |
|---|---|---|
| Round 1 | Warm-up + Internals | 10 min |
| Round 2 | Coding (DS + Functions) | 20 min |
| Round 3 | Deep Python (Closures, Decorators) | 15 min |
| Round 4 | Design + Optimization | 15 min |
You should think aloud while answering.
🔹 ROUND 1 — PYTHON FUNDAMENTALS & INTERNALS (10 MIN)
Q1
Is Python pass-by-value or pass-by-reference? Explain with example.
✔ Expected answer:
Python is call-by-object-reference. Mutation affects the object; rebinding does not.
Q2
def f(x, y=[]):
y.append(x)
return y
print(f(1))
print(f(2))
- Why does this happen?
- How do you fix it?
✔ Must mention definition-time evaluation.
Q3
Why is this slower?
s = ""
for i in range(10000):
s += str(i)
✔ Must mention immutability + O(n²).
Q4
When would you prefer a closure over a class?
✔ Expected:
- Lightweight state
- Encapsulation
- Functional pipelines
🔹 ROUND 2 — CODING ROUND (20 MIN)
🧩 Problem 1 — Frequency-based Logic
Return first non-repeating character index
s = "swiss"
Expected: 0 (s)
✔ Optimal solution (O(n)):
from collections import Counter
def first_unique(s):
freq = Counter(s)
for i, ch in enumerate(s):
if freq[ch] == 1:
return i
return -1
🧩 Problem 2 — Function Behavior (Tricky)
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs])
- Output?
- Why?
- Fix?
✔ Must say late binding + default arg fix.
🧩 Problem 3 — Decorator Coding (Very Common)
Write a decorator that logs function name + execution time.
Expected:
import time
from functools import wraps
def log_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time()-start:.4f}s")
return result
return wrapper
🔹 ROUND 3 — DEEP PYTHON (15 MIN)
Q1
What does this do?
@d1
@d2
def f(): pass
✔ Must say:
f = d1(d2(f))
Q2
Why is functools.wraps important?
✔ Must mention:
- Metadata
- Debugging
- Introspection
- Framework compatibility
Q3
Explain closure memory.
✔ Expected:
- Stored in
__closure__ - Captures references
- Read-only unless
nonlocal
Q4
When would decorators be a bad idea?
✔ Good answers:
- Excessive nesting
- Hidden control flow
- Debugging complexity
- Performance-critical paths
🔹 ROUND 4 — DESIGN & OPTIMIZATION (15 MIN)
Q1
Design a retry mechanism for API calls.
✔ Expect:
- Decorator
- Retry count
- Delay
- Exception handling
Q2
How would you cache expensive function calls?
✔ Must mention:
lru_cache- Closure-based cache
- Eviction strategies
Q3
How would you build a plugin system in Python?
✔ Must mention:
- Function registry
- Decorators
- Dynamic dispatch
🏁 MOCK INTERVIEW VERDICT
If you:
- Explained late binding
- Used Counter / defaultdict
- Wrote decorators cleanly
- Explained closures confidently
👉 You pass Senior Python Interview.
🧠 PART 2 — MINI FRAMEWORK (ADVANCED, INTERVIEW-IMPRESSIVE)
🎯 GOAL
Build a mini task execution framework with:
- Decorator-based registration
- Retry logic
- Timing
- Caching
- Central registry
This is the kind of thing that stands out in interviews.
🧩 STEP 1 — TASK REGISTRY (CORE)
TASKS = {}
def task(name):
def decorator(func):
TASKS[name] = func
return func
return decorator
Usage:
@task("add")
def add(a, b):
return a + b
🧩 STEP 2 — RETRY DECORATOR
import time
def retry(times=3):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(times):
try:
return func(*args, **kwargs)
except Exception as e:
if i == times - 1:
raise
time.sleep(1)
return wrapper
return decorator
🧩 STEP 3 — TIMING DECORATOR
import time
from functools import wraps
def timed(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time()-start:.4f}s")
return result
return wrapper
🧩 STEP 4 — CACHE VIA CLOSURE
def cache(func):
store = {}
def wrapper(x):
if x not in store:
store[x] = func(x)
return store[x]
return wrapper
🧩 STEP 5 — COMPOSE EVERYTHING
@task("square")
@retry(2)
@timed
@cache
def square(n):
print("Computing...")
return n * n
Execution:
print(TASKS)
print(TASKS) # Cached
🔥 WHY THIS FRAMEWORK IS INTERVIEW-GOLD
You can explain:
✅ Decorator stacking order
✅ Closure-based state
✅ Retry + cache patterns
✅ Plugin registration
✅ Separation of concerns
✅ Real production use cases
This is not beginner code.
🧠 WHAT INTERVIEWERS SEE NOW
They don’t see:
“Candidate knows Python”
They see:
“Candidate understands Python as a system.”
🚀 NEXT STEP (FINAL CHOICES)
Reply with ONE number:
1 → Module 5: OOP Mastery (classes, inheritance, MRO, dunder methods — very deep)
2 → Advanced concurrency: threading, multiprocessing, async/await (interview-heavy)
3 → Build a real project (CLI tool / mini backend / ETL engine) using everything learned
You are now well into senior-level Python territory 🔥