Question 1
What is the Python Global Interpreter Lock (GIL) and how does it impact concurrency?
Theoretical Concept
The GIL is a mutex that prevents multiple native threads from executing Python bytecodes simultaneously in CPython.
Comprehensive Answer
CPython memory management is not thread-safe because it relies on reference counting. The GIL ensures only one thread holds control of the Python interpreter at any given moment. Consequently, multi-threaded Python programs do not achieve multi-core CPU parallelism for CPU-bound computations (like mathematical calculations). However, for I/O-bound operations (network requests, disk access), threads release the GIL while waiting, allowing efficient concurrency. For CPU-bound parallelism, the multiprocessing module or process pools must be used.
Code Demonstration
import threading
counter = 0
def increment():
global counter
for _ in range(1_000_000):
counter += 1
# Despite 2 threads, CPython executes them sequentially on 1 CPU core
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
t1.start(); t2.start()
t1.join(); t2.join()
Real-World Enterprise Scenario
A web crawler scraping 500 web pages benefits massively from multithreading (or asyncio) because threads spend 99% of time waiting on network sockets with the GIL released.
Interviewer Pro Tip: Mention that Python 3.13 introduces an experimental free-threaded mode (PEP 703) to optionally disable the GIL for multi-core scaling.
Common Candidate Mistake: Assuming multithreading speeds up heavy machine learning training loops in pure Python without offloading to compiled C libraries like NumPy or PyTorch.
Related follow-up question: What is the difference between multiprocessing and multithreading in Python?
Question 2
Why is using a mutable object as a default parameter in Python a major trap?
Theoretical Concept
Default argument values in Python are evaluated once when the function definition is executed, not each time the function is called.
Comprehensive Answer
If you use a mutable object (like a list or dictionary) as a default parameter, that single object instance is shared across every subsequent call to the function that omits the parameter. Any mutation persists across calls, leading to subtle data leakage bugs. The standard idiom is to use None as the default value and instantiate a new list inside the function body.
Code Demonstration
// BAD: Shared mutable default list
def append_item(item, target_list=[]):
target_list.append(item)
return target_list
print(append_item(1)) # [1]
print(append_item(2)) # [1, 2] -- Unexpected retention!
// GOOD: Safe Python idiom
def safe_append(item, target_list=None):
if target_list is None:
target_list = []
target_list.append(item)
return target_list
Real-World Enterprise Scenario
In an order processing function, defaulting tags=[] resulted in customer notes from earlier transactions bleeding into subsequent unrelated customers orders.
Interviewer Pro Tip: Interviewers love this question because it tests whether you understand Python object binding at definition time versus call time.
Common Candidate Mistake: Writing def add(item, items=[]): and assuming items is re-instantiated on each function call.
Related follow-up question: What are the differences between deepcopy and shallow copy in Python?