Python Full-Stack Interview Questions 11–15 (Python decorators, context managers, monkey patching, GIL, multithreading vs multiprocessing)

This lesson continues building your Python interview foundation by exploring several important intermediate concepts. These topics appear frequently in backend, full-stack, and systems-level discussions. The goal here is to learn not just the definitions, but also how these ideas connect to real-world problem solving and performance considerations.

11. Explain Python decorators.

A decorator in Python is a way to modify or extend the behavior of a function or method without changing its actual code. Think of a decorator as “wrapping” one function inside another. It receives a function as input, adds some behavior, and returns a new function.

Decorators are especially useful when you want to apply the same kind of additional behavior to many different functions, such as logging, authentication checks, caching, or execution timing. Instead of rewriting the same code multiple times, you wrap the function using a decorator.

def greet():
    return "Hello"

# Basic decorator structure
def simple_decorator(func):
    def wrapper():
        print("Function is about to run...")
        result = func()
        print("Function finished running.")
        return result
    return wrapper

# Applying decorator
greet = simple_decorator(greet)

print(greet())

Python also has syntactic sugar for decorators using the @ symbol:

@simple_decorator
def greet():
    return "Hello"
  • They help keep code clean and reusable.
  • Commonly used in frameworks like Django and Flask for request handling, caching, and authentication.

12. Explain context managers in Python.

Context managers allow you to allocate and release resources safely and automatically using the with statement. They are most commonly used when working with files, network connections, database sessions, and locks where certain cleanup steps must happen no matter what.

For example, when working with files, you should always close the file after finishing. A context manager ensures this automatically.

# Using context manager
with open("data.txt", "r") as file:
    content = file.read()
# File automatically closes here

You can also create your own context manager using __enter__ and __exit__:

class CustomManager:
    def __enter__(self):
        print("Entering context")
        return "Resource"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Exiting context")

with CustomManager() as resource:
    print(resource)

13. What is monkey patching in Python?

Monkey patching means changing or extending the behavior of a module or class at runtime without modifying the original source code. It is like temporarily giving a new behavior to an existing function.

This is often used in testing when you want to replace code that interacts with external systems (for example, APIs or databases) with controlled behavior.

class Person:
    def speak(self):
        return "Hello"

def new_speak():
    return "Hi, I have been patched!"

p = Person()

# Monkey patching
Person.speak = new_speak

print(p.speak())  # Output: Hi, I have been patched!
  • Useful for testing and dynamic behavior adjustments.
  • Should be used carefully to avoid confusing code behavior.

14. What is Python’s GIL (Global Interpreter Lock)?

The Global Interpreter Lock is a mechanism in the main Python implementation (CPython) that ensures only one thread executes Python bytecode at a time. Even if your computer has multiple CPU cores, Python threads cannot execute Python code in parallel when the GIL is involved.

The GIL exists mainly to protect Python’s memory management system, making it simpler and safer. However, it limits true CPU-bound parallelism in multithreaded programs.

  • Threads are good for I/O-bound tasks (waiting for files, network, etc.).
  • For CPU-intensive work, multiprocessing is usually a better choice.

15. Explain Python's multithreading vs multiprocessing.

Multithreading and multiprocessing both allow tasks to run concurrently, but they work in different ways and are suitable for different types of workloads.

Multithreading

Threads share the same memory space. They are lightweight and switch quickly. Because of the GIL, Python threads do not run CPU computations truly in parallel, but they work well for tasks that spend time waiting, such as network requests or disk operations.

Multiprocessing

Each process has its own memory and Python interpreter. This bypasses the GIL, allowing true parallel execution on multiple CPU cores. Multiprocessing is ideal for CPU-heavy computations like data processing or machine learning tasks.

# Example of multithreading
import threading

def task():
    print("Running task")

thread = threading.Thread(target=task)
thread.start()
thread.join()
# Example of multiprocessing
from multiprocessing import Process

def task():
    print("Running task")

process = Process(target=task)
process.start()
process.join()
  • Use multithreading for I/O-heavy tasks.
  • Use multiprocessing for CPU-heavy tasks.

Together, these concepts help you build efficient, maintainable, and scalable Python applications.

🚀 Deep Dive With AI Scholar