Navigate by Range
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 hereYou 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(self):
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 standard Python implementation (CPython) that ensures only one thread executes Python bytecode at a time. In a normal build, even on a machine with many cores, Python threads cannot run Python code in parallel.
The GIL exists mainly to protect CPython’s reference-counting memory management, which is not thread-safe without it. It makes the interpreter simpler and single-threaded code faster, at the cost of true CPU-bound parallelism in threads.
- Threads are good for I/O-bound tasks (waiting for files, network, etc.), because the GIL is released while waiting.
- For CPU-intensive work on a standard build, multiprocessing is usually a better choice.
This is changing - and it is the follow-up question to expect
Saying “the GIL means Python can’t do parallelism” is no longer a complete answer. PEP 703 added a free-threaded build of CPython that removes the GIL entirely. It shipped as experimental in Python 3.13 (October 2024) and became an officially supported build in Python 3.14 (October 2025), where it is no longer classed as experimental.
What a strong answer covers:
- It is a separate build, not a flag on the normal one. Free-threaded interpreters are distributed as
python3.14t(thetsuffix). The default download is still the GIL build, so most production code in 2026 still runs with a GIL. - Removing the GIL does not make code thread-safe. It removes the interpreter-wide lock, not your data races. Code that quietly relied on the GIL for atomicity now needs real locks.
- There is a single-threaded cost. Reference counting has to become thread-safe, so free-threaded builds are somewhat slower on single-threaded work - the trade is that they scale across cores.
- C extensions must opt in. Native extensions have to be rebuilt and declare that they support free-threading, which is why ecosystem adoption has been gradual.
Separately, CPython 3.13 also added an experimental JIT compiler (a copy-and-patch tier-2 compiler). It is off by default and the gains are still modest, but it is worth knowing it exists when someone asks “is Python getting faster?”.
The honest summary for an interview: on a default build the GIL still applies, so use processes for CPU-bound work and threads or asyncio for I/O-bound work. But you should know that free-threaded CPython is now a supported option, what it costs, and why it does not remove the need for locks.
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.
Check your understanding
4 questions · no sign-up · misses join your review queue, on this device only
Progress is stored in this browser only - no sign-up, nothing sent anywhere.
Watch these run
Step through the algorithms behind these answers one operation at a time, forwards or backwards, with the code highlighted as it runs.