True A→Z → PhD-level Python Mastery Course Part1

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:

  1. Create stack frame
  2. Bind arguments (a=2, b=3)
  3. Execute body
  4. Return value
  5. 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
SymbolMeaning
/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

  • args is a tuple
  • Immutable
print(type(args))

4.7 **kwargs — VARIABLE KEYWORD ARGUMENTS

def info(**kwargs):
    return kwargs

info(name="Raj", age=30)
  • kwargs is 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)

RoundFocusDuration
Round 1Warm-up + Internals10 min
Round 2Coding (DS + Functions)20 min
Round 3Deep Python (Closures, Decorators)15 min
Round 4Design + Optimization15 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])
  1. Output?
  2. Why?
  3. 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 🔥