Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Python Interview Questions (100+ Deep Dive Q&A)
1. Core Language & Data Structures
1. Python Memory Management (The 3 Pillars)
1. Python Memory Management (The 3 Pillars)
- Private Heap: Where all objects/data live. Managed by Memory Manager. Python abstracts all heap management away from the developer — you never call
mallocdirectly. This is why you cannot control memory layout the way you can in C/Rust. - Raw Memory Allocator: Interaction with OS (
malloc). Used for objects >= 512 bytes. When Python needs large chunks, it goes straight to the OS allocator. - Object-Specific Allocators: Pymalloc (for small objects < 512 bytes). Python pre-allocates arenas of 256KB, divided into 4KB pools, further divided into fixed-size blocks. This dramatically reduces fragmentation and
mallocoverhead for the millions of tiny objects (ints, short strings) a typical Python program creates.
- Reference Counting is immediate — the moment refcount hits 0, memory is freed. This is why Python’s memory behavior is more predictable than Java’s GC pauses.
- Cyclic GC uses a generational approach (3 generations). New objects start in gen0. Objects that survive a collection get promoted. Gen2 collections are expensive and infrequent. You can tune thresholds with
gc.set_threshold(700, 10, 10).
--max-requests to periodically restart workers.Reference Counting Example:- “Your Celery worker’s memory grows from 200MB to 4GB over 12 hours but never shrinks. gc.collect() doesn’t help. What’s happening?” (Tests understanding of arena fragmentation vs actual leaks.)
- “When would you disable the cyclic GC entirely, and what’s the risk?” (Instagram famously disabled GC in production to avoid copy-on-write overhead in forked processes, saving ~10% memory across their fleet.)
- “How does Python’s memory model differ from Java’s, and what are the practical consequences for long-running services?”
2. List vs Tuple vs Set vs Dictionary — When Each Wins
2. List vs Tuple vs Set vs Dictionary — When Each Wins
| Type | Mutable | Ordered | Allow Duplicates | Lookup Cost | Memory |
|---|---|---|---|---|---|
| List | Yes | Yes | Yes | O(n) | Contiguous array of pointers |
| Tuple | No | Yes | Yes | O(n) | Smaller than list (no over-allocation) |
| Set | Yes | No | No | O(1) avg | Hash table, ~3-4x memory of list |
| Dict | Yes | Yes (3.7+) | Keys: No | O(1) avg | Compact dict since 3.6, surprisingly memory-efficient |
- Lists over-allocate by ~12.5% to amortize
append()to O(1). A list of 1M elements uses ~8MB (64-bit pointers) but the actual objects pointed to use far more. - Tuples are immutable, so CPython caches small tuples (length 0-20) for reuse. Creating
(1, 2, 3)repeatedly may return the same object. Tuples are also valid as dict keys and set members because they are hashable (if their contents are hashable). - Sets use open addressing with a hash table. Worst case degrades to O(n) on pathological hash collisions, but Python’s hash randomization (PYTHONHASHSEED) mitigates this for strings since 3.3.
- Dicts since Python 3.6 use a compact representation: a dense array of entries + a sparse hash table of indices. This made dicts 20-25% smaller than Python 2 dicts and insertion-ordered as a side effect (guaranteed in 3.7+).
- Need order + duplicates + index access? List
- Need immutable sequence (dict key, function return)? Tuple
- Need fast “is X in this collection?” membership tests? Set
- Need key-value mapping? Dict
- Need ordered unique elements?
dict.fromkeys(items)(preserves insertion order, deduplicates)
- “You have a list of 50M user IDs and need to check if incoming request IDs are in that list. What structure do you use and what’s the memory impact?” (Set wins on speed but uses ~3-4x memory of a sorted list + bisect approach.)
- “Why can’t you use a list as a dictionary key, and what would you use instead?”
- “When would a
collections.dequebe better than a list, and why?”
3. `__init__` vs `__new__` — Object Creation Deep Dive
3. `__init__` vs `__new__` — Object Creation Deep Dive
__new__: The Allocation phase. It is a static method (though you do not explicitly declare it). Receives the class as first argument. Responsible for creating and returning the instance. This is where memory is actually allocated.__init__: The Initialization phase. Instance method that receives the already-created instance. Sets attributes on it. Does NOT return anything.
Class() call -> type.__call__ -> __new__(cls) -> creates instance -> __init__(instance) -> sets attributes -> returns instance.When you actually override __new__:- Singletons: Return the cached instance instead of creating a new one.
- Immutable types:
int,str,tuplesubclasses must be customized in__new__because by the time__init__runs, the object is already frozen. - Metaclass patterns: ORMs like Django and SQLAlchemy use
__new__in metaclasses to intercept class creation and register models.
__init__ runs every time you call Class(), even if __new__ returns an existing instance. For a proper singleton, you need to guard __init__ with a flag or move all setup into __new__.Red flag answer: “I’ve never needed to override __new__” without being able to explain when you would. Or confusing __new__ with __init__.Follow-up:- “If
__new__returns an instance of a different class, does__init__still run?” (No.__init__only runs if__new__returns an instance of the original class.) - “How does this relate to how Django’s Model metaclass works?”
- “What happens if
__new__returnsNone?”
4. MRO (Method Resolution Order) — The Diamond Problem
4. MRO (Method Resolution Order) — The Diamond Problem
- Subclasses are checked before parents (children first).
- The order of parents in the class definition is preserved (left-to-right).
- A parent class appears only once in the MRO.
ClassName.mro() or help(ClassName) shows the order.Diamond Problem Example:super() matters here: super() does not simply call the parent class — it calls the next class in the MRO. This is crucial for cooperative multiple inheritance:LoginRequiredMixin + ListView + FormMixin all chain via super() through the MRO. Understanding this is essential for debugging why a view is not behaving as expected.Red flag answer: “super() calls the parent class” — this shows a candidate who has only used single inheritance. Or not knowing that Python uses C3 linearization.Follow-up:- “Can you construct a class hierarchy where C3 linearization fails (raises TypeError)?” (Yes:
class X(A, B)andclass Y(B, A)thenclass Z(X, Y)creates an inconsistent MRO.) - “When would you prefer composition over multiple inheritance, and why?” (Most of the time — MI creates tight coupling and makes the codebase harder to reason about.)
- “How does Django’s CBV mixin pattern depend on MRO, and what breaks if you get the mixin order wrong?”
5. Decorators — From Basics to Production Patterns
5. Decorators — From Basics to Production Patterns
@login_required).A decorator is a function that takes a function and returns a modified function. The @ syntax is syntactic sugar.@wraps, debugging becomes a nightmare: stack traces show “wrapper” everywhere instead of actual function names. Sphinx documentation breaks. Introspection-based frameworks fail.Production patterns:- Retry decorators:
@retry(max_attempts=3, backoff=2.0)for flaky network calls - Rate limiting:
@rate_limit(calls=100, period=60)using a token bucket internally - Caching:
@functools.lru_cache(maxsize=1024)— but beware, this holds strong references to arguments and can cause memory leaks with large objects - Auth: Flask’s
@login_required, which redirects unauthenticated users
@wraps. Cannot explain that decorators are just closures.Follow-up:- “How would you write a decorator that works on both sync and async functions?” (You need to inspect
asyncio.iscoroutinefunction(func)and return an async wrapper accordingly.) - “What happens to
@lru_cachein a multi-threaded environment? Is it thread-safe?” (Yes, since Python 3.2,lru_cacheis thread-safe internally. But the cached function itself must be thread-safe.) - “How would you write a decorator that logs execution time and sends it to a metrics service like Datadog?”
6. Generators vs Iterators — Lazy Evaluation in Practice
6. Generators vs Iterators — Lazy Evaluation in Practice
- Iterator: Class implementing
__next__and__iter__. State managed manually. More boilerplate but more control (can implement__len__, customsend(), etc.). - Generator: Function with
yield. State managed automatically by Python (frame object suspended on the stack). Pauses execution at eachyield, resumes on nextnext()call. Extremely memory efficient.
- Processing a 50GB log file? Generator reads line by line: ~0 MB overhead vs 50 GB for
readlines(). - ETL pipeline streaming 100M database rows? Generator yields batches instead of materializing everything.
- Real-time event processing? Generator pipelines chain transformations lazily.
yield from — delegates to a sub-generator, essential for recursive generators:.send() and .throw() — for coroutine-style patterns (pre-asyncio):yield as a suspension mechanism.Follow-up:- “What’s the memory difference between
[x**2 for x in range(10_000_000)]and(x**2 for x in range(10_000_000))?” (List: ~80MB. Generator: ~120 bytes. The generator stores only the frame state.) - “When would you NOT want to use a generator?” (When you need random access, length, or to iterate multiple times. Generators are single-pass.)
- “How do generator pipelines compare to tools like Apache Beam or Spark for data processing?”
7. Context Managers — Resource Management Done Right
7. Context Managers — Resource Management Done Right
__enter__ (setup/acquire) and __exit__ (teardown/release).Exception Handling: __exit__ receives exception details (exc_type, exc_val, exc_tb). Return True to suppress the exception, False (or None) to propagate it.Class-based context manager:contextlib shortcut — the Pythonic way for simple cases:- Temporary directory cleanup:
with tempfile.TemporaryDirectory() as tmpdir: - Database transactions:
with db.begin() as txn: - Lock acquisition:
with threading.Lock(): - Suppressing specific exceptions:
with contextlib.suppress(FileNotFoundError):
with open() pattern. Cannot explain __exit__ parameters or exception suppression.Follow-up:- “What happens if
__enter__itself raises an exception? Does__exit__still run?” (No.__exit__only runs if__enter__completed successfully.) - “How would you write a context manager that acquires multiple locks in a consistent order to avoid deadlocks?”
- “Explain
contextlib.ExitStackand when you’d use it.” (For managing a dynamic number of context managers — e.g., opening N files where N is not known at write-time.)
8. Lambdas — Anonymous Functions and Their Limits
8. Lambdas — Anonymous Functions and Their Limits
lambda x: x * 2.Key limitations:- No statements (no assignment, no
while, noif/elseblocks — only ternarya if cond else b) - Only a single expression
- No type annotations
- Harder to debug (stack traces show
<lambda>instead of a name)
sort, map, filter, max, min.When NOT to use: If the lambda is more than ~40 characters, use a named function. Named functions are self-documenting, testable, and produce better stack traces.- “Why does the closure trap happen? Explain the scoping.” (Lambdas close over the variable, not the value. By the time the lambda runs,
iis 4.) - “In what scenario would
functools.partialbe a better choice than a lambda?” - “How do lambdas interact with Python’s
picklemodule?” (Lambdas cannot be pickled because they have no qualified name. This matters for multiprocessing.)
9. `*args` and `**kwargs` — Flexible Function Signatures
9. `*args` and `**kwargs` — Flexible Function Signatures
*args: Collects extra positional arguments into a tuple.**kwargs: Collects extra keyword arguments into a dictionary. Unpacking:func(*my_list)expands list into positional args.func(**my_dict)expands dict into keyword args.
* in function definition (packing) vs function call (unpacking). Does not know about keyword-only arguments.Follow-up:- “What’s the difference between
def f(a, b)anddef f(a, /, b)in Python 3.8+?” (The/makesapositional-only, preventingf(a=1, b=2).) - “Can you unpack a dictionary into another dictionary? What happens with key conflicts?” (
{**d1, **d2}— later dict wins on conflicts.) - “Why is
**kwargsuseful for maintaining backward compatibility in library APIs?”
10. Shallow vs Deep Copy — The Trap That Bites Everyone
10. Shallow vs Deep Copy — The Trap That Bites Everyone
= copies data.- Assignment (
b = a): No copy at all. Same object, same reference.b is ais True. - Shallow copy (
copy.copy(a)ora[:]orlist(a)): New outer container, but inner objects are still shared references. Fine for flat structures. - Deep copy (
copy.deepcopy(a)): Recursively copies everything. New objects all the way down. Handles circular references via a memo dict.
deepcopy is slow — it traverses the entire object graph. For a nested dict with 1M entries, it can take seconds. In hot paths, consider structural sharing (immutable data structures) or explicit copy logic.Custom copy behavior:b = a does not copy anything in Python. Or thinking shallow copy is “the same as deep copy for simple objects” without qualifying what “simple” means.Follow-up:- “A junior developer reports that modifying a function’s default argument affects subsequent calls. What’s happening?” (Mutable default argument trap — the default list/dict is shared across calls.)
- “When would
deepcopycause an infinite loop, and how does Python prevent it?” (Circular references.deepcopyuses amemodictionary to track already-copied objects.) - “How does the copy behavior interact with
__slots__?” (Objects with__slots__do not have__dict__, socopy.copyuses__getstate__/__setstate__or slot iteration.)
2. Advanced OOP
11. Encapsulation (Public, Protected, Private) — Python's Honor System
11. Encapsulation (Public, Protected, Private) — Python's Honor System
name: Public. Accessible from anywhere. The default._name: Protected (Convention only). “Internal use — don’t touch this unless you know what you’re doing.” Not enforced by the interpreter.__name: Private (Name Mangling). Becomes_ClassName__nameinternally. Not truly private — just harder to accidentally override in subclasses. The purpose is to prevent accidental name collisions in inheritance, NOT to enforce encapsulation.
private).- “If Python doesn’t enforce access control, how do you prevent misuse of internal APIs in a large codebase?” (Linting rules,
__all__exports, documentation, code review, type checkers.) - “How does
__all__interact withfrom module import *?” - “Compare Python’s approach to encapsulation with TypeScript’s
privatekeyword. Which is more useful in practice?”
12. Abstract Base Classes (ABC) — Contracts in Python
12. Abstract Base Classes (ABC) — Contracts in Python
PaymentProcessor directly. Subclass MUST implement all @abstractmethods or you get TypeError at instantiation time (not at call time — fail fast).ABCs vs Protocols:- ABCs (nominal typing): Subclass must explicitly inherit from the ABC. Checked at instantiation.
- Protocols (structural typing, Python 3.8+): Any class with the right methods satisfies the protocol. No inheritance needed. Checked by type checkers like mypy, not at runtime.
- “Can you have abstract properties? Abstract class methods?” (Yes to both:
@property+@abstractmethodcombined,@classmethod+@abstractmethodcombined.) - “How does
collections.abcuse ABCs and why would you inherit fromMappinginstead ofdict?” - “What’s the
__subclasshook__method and when would you implement it?”
13. `__slots__` Optimization — Memory at Scale
13. `__slots__` Optimization — Memory at Scale
__dict__ (a hash table). This uses ~104 bytes on 64-bit Python just for an empty dict, plus the space for key-value pairs.__slots__ = ['x', 'y'] tells Python: “This class ONLY has x and y.” Python allocates a fixed-size array of pointers instead of a dict.Memory savings are dramatic at scale:- Cannot add arbitrary attributes at runtime (no
__dict__) - Cannot use
__weakref__unless you include it in__slots__ - Inheritance gets tricky: if parent uses
__dict__and child uses__slots__, you get BOTH (no savings) - Breaks pickling unless you implement
__getstate__/__setstate__
__slots__ exists but not being able to quantify the memory savings or explain the trade-offs.Follow-up:- “What happens if you define
__slots__in a child class but not the parent?” (The child still has__dict__from the parent. You need__slots__in every class in the hierarchy.) - “When would you choose
__slots__over anamedtupleordataclass(slots=True)?” (Python 3.10+@dataclass(slots=True)gives you slots + all the dataclass conveniences. Prefer it.) - “Have you used
__slots__in production? What was the context?” (Good answer: ORM model instances, graph nodes, particle simulations — anywhere you have millions of uniform objects.)
14. Property Decorators — The Python Way to Do Getters/Setters
14. Property Decorators — The Python Way to Do Getters/Setters
obj.email — looks like an attribute, runs like a method. The caller does not need to know about the validation logic.When properties shine:- Adding validation to an existing attribute without changing the API (backward compatible).
- Computed attributes:
@property def full_name(self)that derives from first + last. - Lazy loading: Expensive computation deferred until first access.
- Deprecation: Property getter that logs a warning before returning the old attribute.
get_email() / set_email() methods in Python. That is Java style, not Pythonic.Follow-up:- “What’s the performance overhead of a property vs direct attribute access?” (~2-5x slower due to descriptor protocol overhead. Usually negligible, but matters in tight loops over millions of iterations.)
- “How are properties implemented under the hood?” (They are descriptors — classes with
__get__,__set__,__delete__methods.) - “How would you implement a cached property that computes once and then behaves like a normal attribute?” (
functools.cached_propertyin 3.8+ — it replaces itself on the instance dict after first computation.)
15. Duck Typing vs Static Typing — The Python Typing Spectrum
15. Duck Typing vs Static Typing — The Python Typing Spectrum
- Duck Typing: “If it has
quack(), it is a Duck.” Python checks nothing at definition time. Errors appear at runtime when a method does not exist. Maximum flexibility, minimum safety. - Static Type Hints:
def quack(d: Duck) -> None:. Checked by tools like mypy, pyright, or pytype at CI time. No runtime enforcement by default (unless you use libraries likebeartypeor Pydantic).
- No types: Quick scripts, prototypes, exploratory data analysis. Fine for <500 lines.
- Partial types: Type public API surfaces (function signatures, class interfaces). Skip internals. Best ROI for most projects.
- Full types: Large codebases (1M+ lines), many contributors, critical infrastructure. Google, Dropbox, Instagram all adopted gradual typing.
- “How does mypy’s
--strictmode differ from default, and when would you turn it on?” (Strict disallows implicitAny, requires return types, etc. Turn it on for new projects; too expensive to retrofit onto legacy code.) - “What’s the difference between
typing.Protocolandabc.ABC?” (Protocol is structural/duck typing with type checker support. ABC is nominal typing with runtime enforcement.) - “Have you seen type hints catch real bugs that tests missed?”
16. Composition vs Inheritance — The Design Principle That Matters Most
16. Composition vs Inheritance — The Design Principle That Matters Most
- Inheritance: “Is-A”.
Dogis anAnimal. Creates a tight coupling between parent and child. Changes to the parent ripple through all descendants (Fragile Base Class problem). - Composition: “Has-A”.
Carhas anEngine. Each component is independent and swappable. Changes are localized.
- True “is-a” relationships where the taxonomy is stable (rarely changes).
- Framework extension points designed for inheritance (Django models, Flask views).
- Abstract base classes defining a contract (abc.ABC).
isinstance() checks frequently, your design is wrong.Red flag answer: “Always use inheritance because it enables code reuse.” Code reuse through inheritance is the most abused pattern in OOP.Follow-up:- “Give me an example of a real codebase where deep inheritance caused problems.” (Django’s old generic views were notoriously hard to customize because of the deep CBV hierarchy. Many teams switched to function-based views for clarity.)
- “How does the Strategy pattern relate to composition?”
- “What is the Liskov Substitution Principle and how does it guide the inheritance vs composition decision?”
17. Method Overloading in Python — It Doesn't Exist (But Here's What Does)
17. Method Overloading in Python — It Doesn't Exist (But Here's What Does)
__dict__.Solutions:- Default arguments:
def process(data, format=None, validate=True) *args/**kwargs: Accept anything, dispatch internally@functools.singledispatch: Type-based dispatch (Python 3.4+)@typing.overload: For type checkers only (not runtime)
@typing.overload for type checker hints (no runtime effect):singledispatch allows third parties to register new types.Follow-up:- “How does
singledispatchdiffer fromsingledispatchmethod(Python 3.8+)?” (The method version works with class methods and respectsself.) - “How would you implement multiple dispatch (dispatching on 2+ argument types)?” (Use libraries like
multipledispatchorplum. Not in stdlib.) - “Why doesn’t Python support overloading natively? What’s the design philosophy behind this?”
18. Singletons — Three Ways and When You Should Not
18. Singletons — Three Ways and When You Should Not
- Module-level: The simplest. Python modules are singletons by nature (cached in
sys.modules). Just put your instance at module level:
__new__override:
- Decorator (see Q5).
- Global mutable state makes testing hard (tests share state, order-dependent).
- Hidden dependencies (functions use the singleton implicitly instead of receiving it as a parameter).
- Thread safety issues (double-checked locking needed in threaded code).
- “How would you make a singleton thread-safe?” (Use
threading.Lock()in__new__, but better yet, use module-level initialization which is inherently thread-safe in Python due to the import lock.) - “How does Django’s
settingsmodule work as a singleton?” - “If singletons are bad, why does Python’s
loggingmodule use them?” (Because the logging hierarchy IS global state by nature — you want one consistent config.)
19. Enum Class — Type-Safe Constants
19. Enum Class — Type-Safe Constants
IntEnum vs Enum: IntEnum members compare equal to integers (Status.ACTIVE == 2 is True). Use this only for backward compatibility with code that expects ints. Prefer Enum for new code.Production pattern — Enum with methods:STATUS_ACTIVE = "active" everywhere instead of enums. Enums prevent typos (Statsu.ACTIVE raises AttributeError), enable IDE autocomplete, and make refactoring safe.Follow-up:- “How do Enums interact with JSON serialization?” (They do not serialize by default. You need a custom encoder or use
.value.) - “What’s
@uniqueand when would you use it?” (Ensures no two members have the same value. Good for catching copy-paste errors.) - “How would you store an Enum in a database column using SQLAlchemy?”
20. Dataclasses (3.7+) — Modern Python Data Containers
20. Dataclasses (3.7+) — Modern Python Data Containers
__init__/__repr__/__eq__ by hand.Boilerplate generator. Auto-generates __init__, __repr__, __eq__, and optionally __hash__, __lt__, etc.| Feature | dataclass | NamedTuple | Pydantic BaseModel |
|---|---|---|---|
| Mutable | Yes (default) | No | Yes |
| Validation | Manual (__post_init__) | None | Automatic + coercion |
| Performance | Fast | Fastest | Slower (validation overhead) |
| Serialization | Manual | _asdict() | .model_dump(), .model_dump_json() |
| Use case | Internal data | Simple immutable records | API request/response models |
@dataclass(slots=True)— auto-generates__slots__for memory savings@dataclass(kw_only=True)— all fields keyword-only (prevents positional arg confusion)@dataclass(match_args=True)— enables structural pattern matching
- “What’s the gotcha with mutable default values in dataclasses?” (Must use
field(default_factory=list)instead oftags: list = []— same mutable default trap as regular functions.) - “When would you choose Pydantic over dataclasses?” (When you need runtime validation, JSON serialization, or are building APIs with FastAPI.)
- “How does
frozen=Truemake a dataclass hashable and what are the performance implications?” (Frozen dataclasses implement__hash__based on all fields. But computing hash on every dict/set operation can be expensive for dataclasses with many fields.)
3. Asyncio & Concurrency
21. Threading vs Multiprocessing vs Asyncio — The Decision Framework
21. Threading vs Multiprocessing vs Asyncio — The Decision Framework
| Model | Mechanism | CPU Cores | Best For | Overhead |
|---|---|---|---|---|
| Threading | OS threads, shared memory | 1 (GIL) | I/O blocking (network, disk) | ~8MB stack per thread |
| Multiprocessing | OS processes, separate memory | All | CPU-heavy (number crunching) | Process spawn + IPC |
| Asyncio | Single thread, cooperative | 1 | Massive I/O (10K+ connections) | ~2KB per coroutine |
- Is the bottleneck CPU computation (matrix math, image processing, hashing)? -> multiprocessing or C extension (NumPy, Rust via PyO3)
- Is the bottleneck I/O wait with <100 concurrent operations? -> threading (simpler to reason about)
- Is the bottleneck I/O wait with 1000+ concurrent operations? -> asyncio (threads do not scale to 10K)
- Do you need both CPU and I/O concurrency? -> asyncio +
ProcessPoolExecutorfor CPU offloading
- Sequential: 10,000 * 0.5s = 5,000 seconds (83 minutes)
- Threading (100 threads): ~50 seconds. But 100 threads = 800MB stack memory.
- Asyncio (10,000 coroutines): ~5 seconds. 10,000 coroutines = ~20MB total.
- “You have a FastAPI endpoint that calls a machine learning model (CPU-bound) and then writes results to S3 (I/O-bound). How do you architect this?” (Use
loop.run_in_executor(ProcessPoolExecutor, model.predict, data)for the ML call, thenawaitthe async S3 upload.) - “What happens if you create 10,000 OS threads in Python? At what point does threading break down?” (Context switching overhead + stack memory. Typically >1000 threads causes thrashing.)
- “How does Python 3.12+‘s per-interpreter GIL / free-threaded Python (3.13) change this picture?”
22. GIL (Global Interpreter Lock) — The Most Misunderstood Thing in Python
22. GIL (Global Interpreter Lock) — The Most Misunderstood Thing in Python
- I/O operations releasing the GIL (network, disk, sleep). Threads waiting on I/O do not hold the GIL.
- C extensions releasing the GIL (NumPy, OpenCV, hashlib all release it during computation).
- Multiprocessing (separate processes, separate GILs).
- CPU-bound: Use
multiprocessingor C extensions that release the GIL - I/O-bound: Use
threadingorasyncio(GIL released during I/O wait)
--disable-gil). Python 3.14+ makes it more stable. This is the biggest change to CPython’s execution model in 30 years. But it requires all C extensions to be updated for thread safety, so adoption will be gradual.Red flag answer: “The GIL makes Python single-threaded.” This confuses concurrent with parallel. Python IS concurrent (threading, asyncio) but NOT parallel for CPU-bound Python bytecode (until free-threading).Follow-up:- “Instagram runs one of the largest Python deployments in the world. How do they work around the GIL?” (Multi-process model with gunicorn, shared-nothing architecture, memory savings via disabling GC in forked workers.)
- “Why did CPython choose reference counting + GIL instead of tracing GC without a GIL?” (Reference counting gives deterministic destruction and lower latency, but requires the GIL for thread safety. Tracing GC has stop-the-world pauses but enables true parallelism.)
- “How does
nogil/ free-threaded Python 3.13 handle reference counting without a GIL?” (Biased reference counting, deferred reference counting, and per-object locks for containers.)
23. Event Loop — The Heart of Asyncio
23. Event Loop — The Heart of Asyncio
await things.The event loop is an infinite loop that:- Checks for ready callbacks/tasks
- Polls for I/O completions (using
epollon Linux,kqueueon macOS,IOCPon Windows) - Runs ready callbacks
- Repeats
async def) yield control (await) when they hit an I/O operation. The loop then runs other tasks. When the I/O completes, the loop resumes the coroutine.Critical mental model: There is NO preemption. If a coroutine does not await, it holds the loop hostage. This is both asyncio’s strength (no race conditions between await points) and weakness (one blocking call freezes everything).asyncio.run()creates a new event loop, runs the coroutine, and closes the loop. Use this as the entry point.loop.call_soon()schedules a callback for the next iteration.loop.call_later(delay, callback)schedules after a delay.loop.create_future()creates a low-level Future (the primitive that Tasks are built on).
- “What happens internally when you
await asyncio.sleep(1)?” (The coroutine registers a callback withcall_later, yields control, and the loop resumes it after 1 second.) - “How does the event loop integrate with OS-level I/O multiplexing (
epoll/kqueue)?” (The loop wrapsselectorsmodule which abstractsepoll/kqueue/select. File descriptors are registered for read/write readiness.) - “Can you have multiple event loops in one process?” (Possible but discouraged. Use
asyncio.run()for the main loop. Background threads can have their own loop viaasyncio.new_event_loop().)
24. `await` Keyword — Suspension Points and Concurrency
24. `await` Keyword — Suspension Points and Concurrency
await is the point where concurrency happens in asyncio. Everything between awaits is atomic.await means: “Pause execution of this coroutine, yield control to the event loop, and resume when the awaited result is ready.”Can only be used inside async def. Can only await awaitables (coroutines, Tasks, Futures, objects with __await__).The key insight most candidates miss:awaits, your code runs atomically. But across awaits, other tasks can interleave. This is where asyncio race conditions live.Common pitfalls:- Forgetting to
await:result = fetch_data()gives you a coroutine object, not the result. No error, just wrong behavior. - Awaiting in a loop sequentially when you want concurrency:
for url in urls: await fetch(url)is sequential. Useasyncio.gather()orasyncio.TaskGroup(3.11+).
- “What is the difference between
await coro()andasyncio.create_task(coro())?” (awaitsuspends and waits.create_taskschedules it to run concurrently and returns a Task handle you can await later.) - “What happens if you never
awaita coroutine?” (It never runs. Python will emit aRuntimeWarning: coroutine was never awaited.) - “How does
asyncio.TaskGroup(Python 3.11+) improve onasyncio.gather()?” (Structured concurrency — if one task fails, all others are cancelled and the exception propagates cleanly.)
25. Race Conditions in Asyncio — Yes, They Exist
25. Race Conditions in Asyncio — Yes, They Exist
await points, races happen.asyncio.Lock:asyncio.Semaphore(n): Limit concurrent access to n (e.g., rate limiting API calls)asyncio.Event(): One task signals, others waitasyncio.Queue(): Producer-consumer pattern (bounded, prevents backpressure)asyncio.Condition(): Complex wait/notify patterns
awaits is atomic. If your shared-state read and write are separated by an await, you need a lock.Red flag answer: “Asyncio is single-threaded so it can’t have race conditions.” This is dangerously wrong and will lead to data corruption bugs.Follow-up:- “How do asyncio locks differ from threading locks?” (Asyncio locks are not OS-level mutexes — they are cooperative. A coroutine holding an asyncio lock will not block the event loop; it will
awaituntil the lock is available.) - “How would you implement a rate limiter using
asyncio.Semaphore?” - “What’s the asyncio equivalent of a thread-safe queue for producer-consumer patterns?”
26. Blocking Code in Async — The Silent Killer
26. Blocking Code in Async — The Silent Killer
requests.get()— useaiohttporhttpxinsteadtime.sleep()— useasyncio.sleep()- Synchronous DB drivers (
psycopg2) — useasyncpgordatabases - File I/O (
open().read()) — useaiofilesorrun_in_executor - DNS resolution (hidden blocking in
socket.getaddrinfo) — useaiodns
loop.slow_callback_duration (default 0.1s). The loop logs warnings when a callback takes too long. In production, instrument with tools like aiomonitor or custom middleware that times each request handler.Red flag answer: Not knowing that requests.get() blocks the event loop, or not knowing about run_in_executor.Follow-up:- “Your FastAPI service’s p99 latency spikes from 50ms to 30 seconds intermittently. All endpoints are async. How do you diagnose?” (Likely a blocking call somewhere. Enable
PYTHONASYNCIODEBUG=1to get warnings. Check for sync DB drivers, sync HTTP calls, or CPU-bound code in handlers.) - “What’s the difference between
ThreadPoolExecutorandProcessPoolExecutorinrun_in_executor?” (Thread pool for blocking I/O, process pool for CPU-bound. Thread pool shares GIL, process pool does not.) - “How does Starlette/FastAPI handle sync route handlers vs async ones?” (Sync handlers are auto-wrapped in
run_in_executorwith a thread pool. Async handlers run directly on the event loop.)
27. `asyncio.gather` vs `TaskGroup` — Concurrent Task Execution
27. `asyncio.gather` vs `TaskGroup` — Concurrent Task Execution
asyncio.gather — runs multiple awaitables concurrently, waits for all to finish:asyncio.TaskGroup (Python 3.11+) — structured concurrency, the modern replacement:- Error handling:
gatherwithreturn_exceptions=Falsecancels remaining tasks on first failure but can leave orphaned tasks.TaskGroupuses structured concurrency — clean cancellation of all tasks on any failure. - Cancellation:
TaskGroupcancels siblings automatically.gatherrequires manual handling. - Exception type:
TaskGroupraisesExceptionGroup(PEP 654), enabling handling of multiple simultaneous failures.
gather: Simple fan-out where you want all results, fine with legacy code.TaskGroup: New code on Python 3.11+. Prefer it for correctness.
TaskGroup or not understanding the error handling difference.Follow-up:- “What is
ExceptionGroupand how do you catch specific exceptions from it?” (Useexcept*syntax:except* ValueError as eg:catches only ValueError instances from the group.) - “How would you limit concurrency to 10 simultaneous tasks when processing 10,000 URLs?” (Use
asyncio.Semaphore(10)inside each task, or batch with manual chunking.) - “What happens if you cancel the parent task while a
TaskGroupis running?”
28. Async Context Managers and Async Iterators
28. Async Context Managers and Async Iterators
@asynccontextmanager shortcut:__enter__/__exit__ and __aenter__/__aexit__, or when you need the async variants.Follow-up:- “When must you use
async withvs regularwith?” (When the setup or teardown involves I/O — DB connections, network sockets, file I/O withaiofiles.) - “How would you implement backpressure in an async generator that produces data faster than the consumer can handle?” (Use an
asyncio.Queuewith amaxsizebetween producer and consumer.) - “What’s the difference between
async forand callinggatheron a list of tasks?”
29. UVLoop — Making Asyncio Production-Fast
29. UVLoop — Making Asyncio Production-Fast
libuv (the same C library that powers Node.js’s event loop). It is written in Cython and provides 2-4x performance improvement for asyncio applications.libuv. The biggest gains come from:- Faster I/O polling (efficient
epoll/kqueuewrappers) - Faster timer management
- Reduced Python-level overhead per iteration
- HTTP requests/sec: ~2.5x improvement
- WebSocket throughput: ~3x improvement
- TCP echo server: ~4x improvement
asyncio.subprocess on some platforms.Red flag answer: “I’ve never heard of uvloop” is okay for a junior, but a red flag for anyone claiming production asyncio experience.Follow-up:- “What other performance optimizations would you apply to a production asyncio service?” (Connection pooling with
asyncpg/aioredis,orjsoninstead ofjson, HTTP/2 viahypercorn,--workersfor multi-process.) - “How does UVLoop compare to the new
asyncioimprovements in Python 3.12+?” (CPython has been steadily improving the default loop. The gap is narrowing but UVLoop is still faster.) - “When might UVLoop cause problems?” (C extension incompatibilities, debugging is harder since the loop is opaque C code, and some asyncio debug features do not work.)
4. Backend & Web (Django/FastAPI)
30. WSGI vs ASGI — The Foundation of Python Web
30. WSGI vs ASGI — The Foundation of Python Web
- WSGI (Web Server Gateway Interface): Synchronous standard (PEP 3333). One request = one thread/process. Used by Flask, Django (traditional), Bottle. Cannot handle WebSockets or long-polling natively.
- ASGI (Asynchronous Server Gateway Interface): Async standard. Supports HTTP, WebSockets, HTTP/2, Server-Sent Events. Used by FastAPI, Django Channels, Starlette.
- “Can you run a Django app on both WSGI and ASGI? What changes?” (Yes. Django 3.0+ supports both. ASGI enables Channels for WebSockets. But ORM calls are still sync by default — need
sync_to_asyncor async ORM in Django 4.1+.) - “What’s the role of Daphne in the Django ASGI ecosystem?”
- “How does ASGI handle the lifespan protocol (startup/shutdown events)?”
31. FastAPI Features — Why It Won the Python API Framework War
31. FastAPI Features — Why It Won the Python API Framework War
- Speed: Built on Starlette (ASGI) + Uvicorn. Native async support. On par with Node.js/Go for I/O-bound workloads.
- Validation: Pydantic models for automatic request/response validation with detailed error messages. Coerces types (string “42” -> int 42).
- Auto Docs: Swagger UI + ReDoc generated from type hints. Zero extra code.
- Dependency Injection: Powerful DI system via
Depends(). Handles auth, DB sessions, rate limiting, feature flags. Dependencies can be async. - Type-first: Uses Python type hints as the source of truth for validation, serialization, and documentation.
- FastAPI: Async-first, type-driven, best for APIs. No built-in ORM/admin.
- Django REST: Batteries-included (ORM, admin, auth). Sync-first. Best for full web apps.
- Flask: Minimal, sync, maximum flexibility. Best for microservices that need custom everything.
- “How does FastAPI’s Depends system handle nested dependencies and caching within a request?” (Dependencies are resolved as a DAG.
Depends(func, use_cache=True)ensures a dependency is only called once per request even if multiple endpoints depend on it.) - “What’s the performance difference between a sync and async FastAPI endpoint?” (Sync endpoints are run in a thread pool, adding ~0.1ms overhead. For CPU-bound handlers, sync is fine. For I/O-bound, async avoids the thread pool overhead.)
- “How would you handle background tasks in FastAPI?” (
BackgroundTasksfor lightweight work, Celery/Dramatiq for heavy work.)
32. Django ORM N+1 Problem — The Query That Kills Performance
32. Django ORM N+1 Problem — The Query That Kills Performance
django-debug-toolbar: Shows query count per page. If you see 200+ queries, you have N+1.nplusonelibrary: Raises exceptions on N+1 queries in development.LOGGINGsetting withdjango.db.backendslogger: Logs every SQL query.
select_related everywhere.” Over-joining can be worse than N+1 for large tables. You need to measure and choose between select_related (JOIN) and prefetch_related (2 queries) based on data shape.Follow-up:- “When is
prefetch_relatedbetter thanselect_related?” (When the related set is large or ManyToMany. JOINs create cartesian products that can explode result set size. Prefetch keeps queries separate.) - “How does Django 4.2+‘s async ORM change N+1 detection?” (Async ORM makes it even easier to accidentally trigger N+1 because attribute access in async context raises
SynchronousOnlyOperation.) - “How would you handle N+1 in a GraphQL API using Django?” (Use
graphene-djangowithDataLoaderpattern to batch and cache related object fetches.)
33. Middleware — Request/Response Processing Pipeline
33. Middleware — Request/Response Processing Pipeline
- Authentication: Verify JWT/session before the request reaches the view.
- Logging/Metrics: Log request duration, status codes to Datadog/Prometheus.
- CORS: Add
Access-Control-Allow-Originheaders. - Rate Limiting: Track requests per IP, return 429 if exceeded.
- Request ID Injection: Generate UUID, attach to request, include in all logs for tracing.
- Compression: GZip responses above a size threshold.
- “How do you handle middleware that needs to read the request body? What’s the gotcha?” (Request body is a stream — once read, it is consumed. You need to cache and re-attach it. In Starlette:
body = await request.body()caches it.) - “What’s the difference between Django middleware and ASGI middleware?”
- “How would you implement a circuit breaker as middleware?”
34. CSRF in Django — Understanding the Attack
34. CSRF in Django — Understanding the Attack
csrf_token in templates.Cross Site Request Forgery: An attacker tricks a logged-in user’s browser into making an unwanted request to your server. The browser automatically sends cookies (including session cookies), so your server thinks it is a legitimate request.Django’s defense (double-submit cookie pattern):- Django sets a
csrftokencookie. - For POST/PUT/DELETE requests, Django requires the token in either the form data (
csrfmiddlewaretoken) OR theX-CSRFTokenheader. - Server verifies the submitted token matches the cookie token.
@csrf_exempt: API endpoints using token-based auth (JWT, API keys). Since there is no cookie-based session, CSRF is not applicable. But never exempt cookie-authenticated endpoints.Red flag answer: “Just disable CSRF, it causes problems with AJAX.” This is a security vulnerability waiting to happen.Follow-up:- “Why doesn’t SameSite cookie attribute make CSRF tokens unnecessary?” (SameSite=Lax still allows GET requests from cross-origin. SameSite=Strict breaks legitimate use cases like following links. CSRF tokens are defense-in-depth.)
- “How does CSRF protection work differently for API-only backends using JWT?”
- “What’s the difference between CSRF and XSS, and why does protecting against one not protect against the other?”
35. Gunicorn vs Uvicorn — Production Deployment Architecture
35. Gunicorn vs Uvicorn — Production Deployment Architecture
python app.py locally.- Gunicorn: A pre-fork worker model process manager. Spawns multiple worker processes, each handling requests independently. Handles worker lifecycle (restart on crash, graceful reload). Does NOT understand async.
- Uvicorn: An ASGI server. Runs async Python code on the event loop. Single-process by default.
- Production setup: Gunicorn as process manager, Uvicorn as the worker class:
workers = 2 * CPU_CORES + 1 for sync workers. For async workers (Uvicorn), fewer workers needed since each handles many concurrent requests: workers = CPU_CORES is usually sufficient.Why --max-requests: Python processes accumulate memory over time (fragmentation, caches, leaked references). --max-requests 10000 with --max-requests-jitter 1000 restarts workers after 10,000-11,000 requests, preventing gradual memory growth. The jitter prevents all workers from restarting simultaneously.Red flag answer: Running Uvicorn directly in production (uvicorn app:app). No process management, no worker restart, no graceful shutdown.Follow-up:- “What happens during a graceful reload (
kill -HUP) of Gunicorn?” (New workers spawn with new code, old workers finish current requests and die. Zero-downtime deployment.) - “How does this compare to running behind nginx?” (Nginx handles TLS termination, static files, load balancing, and connection buffering. Gunicorn/Uvicorn handles Python app logic.)
- “When would you use Hypercorn instead of Uvicorn?” (HTTP/2 support, HTTP/3/QUIC support, more ASGI features.)
36. REST vs GraphQL — API Design Trade-offs
36. REST vs GraphQL — API Design Trade-offs
| Aspect | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple (/users, /posts) | Single (/graphql) |
| Data shape | Fixed by server | Defined by client |
| Over-fetching | Common (all fields returned) | Eliminated (request only needed fields) |
| Under-fetching | Common (need multiple calls) | Eliminated (nested queries) |
| Caching | HTTP caching (CDN, browser) works natively | Complex (no URL-based caching, need Persisted Queries) |
| Versioning | URL or header versioning (/v2/users) | Schema evolution (deprecate fields) |
| Tooling | Mature, universal | Growing, sometimes heavy (Apollo, Relay) |
- Simple CRUD APIs with well-defined resources
- Public APIs where caching at CDN/proxy level matters (GitHub, Stripe, Twilio all use REST)
- Team does not have GraphQL expertise
- Multiple client types needing different data shapes (mobile vs web vs internal dashboard)
- Complex nested data (e.g., social graph: user -> friends -> posts -> comments)
- Rapid frontend iteration without backend changes
- “How do you prevent a malicious GraphQL query like
user -> friends -> friends -> friends ...(depth 100) from taking down your server?” (Query depth limiting, query complexity analysis, persisted queries, timeout enforcement.) - “How does caching work in GraphQL vs REST?” (REST: HTTP caching headers + CDN. GraphQL: Apollo Client normalized cache, Persisted Queries for CDN, Dataloader for server-side batching.)
- “What about gRPC? When would you choose it over both REST and GraphQL?”
37. DB Migrations — Version Control for Your Schema
37. DB Migrations — Version Control for Your Schema
- Always make migrations reversible. Write the
downmigration. You will need it when a deploy goes wrong at 2 AM. - Never modify a migration that has been applied to production. Create a new migration instead.
- Separate schema changes from data migrations. Schema = add column. Data = backfill column. Different migrations, different risk profiles.
- Use online schema changes for large tables. Adding a column to a 500M-row table with a default value locks the table in PostgreSQL <11. Use
ADD COLUMN ... DEFAULT NULLthen backfill, or usepg_repack/gh-ost.
migrate and it works.” Shows no awareness of zero-downtime migration strategies or the risks of schema changes on large production databases.Follow-up:- “How would you rename a column in production without downtime?” (Add new column, dual-write, backfill, switch reads, drop old column. Multiple deploys over days.)
- “What’s the difference between
op.execute()for raw SQL in migrations vs Django’sRunPython?” - “How do you handle migration conflicts when two developers add migrations to the same app simultaneously?”
38. Session vs JWT Auth — The Statefulness Trade-off
38. Session vs JWT Auth — The Statefulness Trade-off
| Aspect | Session | JWT |
|---|---|---|
| State | Server-side (Redis/DB) | Client-side (token contains claims) |
| Revocation | Instant (delete session) | Hard (token valid until expiry) |
| Scalability | Requires shared session store | Stateless, any server can verify |
| Size | Small cookie (~32 bytes ID) | Large cookie (~800+ bytes for payload + signature) |
| XSS Risk | Cookie with HttpOnly flag is safe | If stored in localStorage, vulnerable to XSS |
| CSRF Risk | Vulnerable (cookie auto-sent) | Not vulnerable if sent in header |
- Sessions: Traditional web apps, need instant revocation (admin bans user), simpler security model.
- JWT: Microservices (avoid shared session store), mobile apps, third-party auth, short-lived access + long-lived refresh token pattern.
- “How do you implement ‘log out of all devices’ with JWTs?” (Increment a
token_versionin the user record. All existing tokens with old version are rejected. Requires a DB check per request — partially defeats statelessness.) - “What’s the refresh token rotation pattern and why is it important?”
- “How does OAuth 2.0’s token model differ from simple JWT auth?”
39. Dependency Injection — Testable, Composable Code
39. Dependency Injection — Testable, Composable Code
unittest.mock.patch.” Excessive mocking is a code smell — it means your code is too tightly coupled. DI reduces the need for mocking.Follow-up:- “How does FastAPI’s
Dependshandle dependency caching within a single request?” (By default, a dependency called multiple times in the same request is cached — called once, result reused.) - “What’s the difference between DI and the Service Locator pattern?”
- “How would you implement DI in a Django project that doesn’t have a built-in DI framework?” (Use constructor injection, factory functions, or libraries like
django-injectorordependency-injector.)
5. Coding Scenarios & Snippets
40. Flatten Nested List (Recursive) — Beyond the One-Liner
40. Flatten Nested List (Recursive) — Beyond the One-Liner
- Empty lists:
flatten([])->[] - Non-list iterables: Should
flatten("hello")flatten to['h','e','l','l','o']? Usually no. Checkisinstance(item, (list, tuple))specifically. - Recursion depth: Python default limit is 1000. For deeply nested structures, use an iterative approach with an explicit stack.
- “What happens if you flatten a list nested 2000 levels deep with the recursive approach?” (
RecursionError. Use iterative version.) - “How would you modify this to flatten arbitrary iterables (tuples, generators) but NOT strings?”
41. LRU Cache Implementation — Data Structure Design
41. LRU Cache Implementation — Data Structure Design
- Get(key): O(1). Look up in hash map, move node to head (most recently used).
- Put(key, value): O(1). Add to head. If at capacity, remove tail (least recently used).
@functools.lru_cache(maxsize=128) — thread-safe, C-optimized.Production considerations:lru_cacheholds strong references to all arguments. If arguments are large objects, you leak memory. Useweakrefor manual cache management.- For distributed systems, use Redis with TTL instead of in-process LRU.
- Consider TTL (time-to-live) expiration in addition to LRU eviction.
functools.lru_cache.Follow-up:- “What’s the difference between LRU, LFU, and FIFO eviction? When would you choose each?” (LRU for recency-biased workloads, LFU for frequency-biased, FIFO for simplicity.)
- “How would you make this cache work across multiple processes?” (External cache like Redis or Memcached.)
- “What’s
functools.cache(Python 3.9+) and how does it differ fromlru_cache?” (Unbounded cache — no maxsize. Equivalent tolru_cache(maxsize=None)but cleaner.)
42. Reading Huge Files — Production File Processing
42. Reading Huge Files — Production File Processing
f.read() or f.readlines() on large files. Not knowing about chunked reading or memory mapping.Follow-up:- “How would you process a 500GB file that does not fit on a single machine?” (Distributed processing: Spark, Dask, or split + parallel process.)
- “What’s the difference between
mmapand regular file reading for a search operation?” - “How does Python’s file iterator interact with OS-level buffering?”
43. Sort List of Dicts by Key — Practical Python
43. Sort List of Dicts by Key — Practical Python
operator.itemgetter vs lambda.sorted() vs .sort():sorted(data): Returns NEW list, original unchanged. Works on any iterable.data.sort(): In-place, returnsNone. Only works on lists. Slightly more memory efficient.
sorted() and .sort(), or using bubble sort manually.Follow-up:- “How would you sort a list of objects where some might be missing the key?” (Use
key=lambda x: x.get('age', float('inf'))to push missing to the end.) - “Why is Timsort particularly good for real-world data?” (Exploits existing runs of sorted data, which are common in practice.)
6. Edge Cases & Trivia
44. `is` vs `==` — Identity vs Equality
44. `is` vs `==` — Identity vs Equality
==: Value equality. Calls__eq__. Two different objects can be equal.is: Identity equality. Checks if same object in memory (id(a) == id(b)).
is: Only for singletons: None, True, False.is for string or number comparison (“it works in my tests” — because of interning, which is an implementation detail).Follow-up:- “Why does CPython cache small integers?” (Performance. Small ints are used constantly. Caching avoids millions of allocations.)
- “What is string interning and when does Python do it?” (Short strings that look like identifiers are cached.
"hello" is "hello"may be True, but"hello world" is "hello world"may not.) - “Can you override
isbehavior?” (No.isalways checksid(). Only==is customizable via__eq__.)
45. Mutable Default Arguments — Python's Most Famous Gotcha
45. Mutable Default Arguments — Python's Most Famous Gotcha
[] is a single list object stored in func.__defaults__.- “Where is the default value stored, and how would you inspect it?” (
func.__defaults__for positional,func.__kwdefaults__for keyword-only.) - “Is this behavior a bug or a feature? Defend your answer.” (Feature — it enables memoization patterns and was a deliberate design choice. But it violates principle of least surprise.)
46. Python Bytecode — Under the Hood
46. Python Bytecode — Under the Hood
- Compilation:
.pysource ->.pycbytecode (stored in__pycache__/). This is a stack-based instruction set, NOT machine code. - Interpretation: CPython’s virtual machine executes the bytecode instruction by instruction.
- Performance tuning: Understanding bytecode helps you write faster Python.
x = x + 1generates different bytecode thanx += 1(INPLACE_ADD is optimized for some types). - Debugging:
discan reveal why two seemingly identical pieces of code behave differently. - Security: Bytecode can be decompiled (
uncompyle6), so.pycfiles are NOT protection for proprietary code.
- “What’s the difference between CPython bytecode and Java bytecode?” (CPython bytecode is not standardized — it changes between Python versions. Java bytecode is a stable contract.)
- “How does PyPy’s JIT compiler relate to bytecode?” (PyPy traces the bytecode execution, identifies hot loops, and compiles them to machine code at runtime.)
- “Can you modify bytecode at runtime?” (Yes, using the
bytecodelibrary or manualcodeobject manipulation. Used by some testing frameworks and code instrumentation tools.)
47. Monkey Patching — Runtime Modification
47. Monkey Patching — Runtime Modification
- Testing:
unittest.mock.patchis structured monkey patching. Replace external service calls with mocks. - Hotfixing: Patch a third-party library bug without waiting for a release.
- Instrumentation: Add timing/logging to library functions (APM tools like Datadog do this).
- Other code that imported the original function before your patch will not see the change (they hold a direct reference).
- Breaks IDE navigation and static analysis.
- Makes debugging extremely difficult (“where did this behavior come from?”).
- Upgrade the library and your patch silently does the wrong thing.
- “How does
unittest.mock.patchhandle the import-order problem?” (You must patch where the function is looked up, not where it is defined:@patch('module_a.get')not@patch('requests.get').) - “What’s
gevent.monkey.patch_all()and why is it so controversial?” (Patches stdlib to be async-compatible. Powerful but makes debugging nearly impossible.) - “How would you patch a C extension function?” (You generally cannot — C functions are not Python objects. You must wrap at the Python level.)
48. Pickling Security — Deserialization Attacks
48. Pickling Security — Deserialization Attacks
- NEVER unpickle data from untrusted sources (user input, network, external APIs).
- Use JSON, MessagePack, or Protocol Buffers for data exchange.
- If you must use pickle (ML models, caching), use HMAC signing to verify integrity.
- Use
pickle.Unpicklerwithfind_classoverride to whitelist allowed classes.
- Redis/Memcached caching (if pickle is the serializer)
- Celery task arguments (default serializer was pickle, now JSON)
- ML model files (
.pkl,.joblib) - Python’s
shelvemodule
- “How would you safely load an ML model file from an untrusted source?” (Use SafeTensors for model weights. Verify file hash. Run in a sandboxed environment.)
- “Why did Celery change its default serializer from pickle to JSON?”
- “How does
__reduce__enable code execution, and can you prevent it?” (__reduce__returns a callable + args for reconstruction. Restrict with customUnpickler.find_class.)
49. PyPy vs CPython — When Alternative Interpreters Win
49. PyPy vs CPython — When Alternative Interpreters Win
- CPython: The standard (reference) interpreter. Written in C. Uses reference counting + cyclic GC. Executes bytecode on a stack-based VM.
- PyPy: Alternative interpreter with a JIT (Just-In-Time) compiler. Written in RPython. Uses tracing JIT that compiles hot loops to machine code at runtime.
- Pure Python code: PyPy is typically 4-10x faster than CPython.
- Numeric computation: CPython + NumPy is often faster than PyPy (NumPy’s C extensions are already optimized).
- Startup time: PyPy is slower to start (JIT warm-up).
- Long-running servers with CPU-bound Python code
- Algorithmic code that cannot easily use NumPy/C extensions
- When you cannot rewrite in Cython/Rust
- Heavy use of C extensions (NumPy, SciPy, pandas) — compatibility issues via
cpyext(slow compatibility layer) - Short-running scripts (JIT warmup negates benefits)
- When you need the latest Python version (PyPy typically lags 1-2 versions)
- GraalPython: On GraalVM, good Java interop
- Cython: Compile Python to C (not an interpreter but a compiler)
- Mypyc: Compile type-annotated Python to C extensions
- “How does PyPy’s tracing JIT differ from Java’s HotSpot JIT?” (PyPy traces through entire loops including function calls, generating specialized machine code. HotSpot compiles individual methods.)
- “What is the free-threaded CPython (3.13+) and does it make PyPy less relevant?”
- “When would you choose Cython over PyPy for performance?” (When you need C-level speed for specific functions while keeping CPython compatibility for the rest.)
7. Python Medium Level Questions
50. List Comprehensions vs Generator Expressions — Memory vs Speed
50. List Comprehensions vs Generator Expressions — Memory vs Speed
- Need to iterate once? Generator expression. Saves memory.
- Need to index, slice, or iterate multiple times? List comprehension.
- Need length? List comprehension (generators have no
len()). - Passing to a function that consumes once (
sum,max,''.join)? Generator expression.
- “Can you nest generator expressions? What are the readability trade-offs?” (Yes, but beyond one level of nesting, use explicit loops for clarity.)
- “What’s the performance difference between
sum(x for x in range(N))andsum(range(N))?” (The latter is faster becausesumhas a fast path forrangeobjects.)
51. Collections Module — Beyond Built-in Data Structures
51. Collections Module — Beyond Built-in Data Structures
- Counter: Log analysis, word frequency, histogram data.
- defaultdict: Building adjacency lists, grouping records by key.
- deque: Sliding windows, BFS queues, bounded buffers, undo history.
- ChainMap: Layered configuration (env vars > config file > defaults).
if key not in dict: dict[key] = [] instead of using defaultdict(list).Follow-up:- “What’s the time complexity difference between
deque.appendleft()andlist.insert(0, x)?” (deque: O(1). list: O(n) because it shifts all elements.) - “When would you use
Countersubtraction?” (c1 - c2removes counts. Useful for finding “what’s missing” in inventory systems.) - “How does
defaultdicthandle nested defaults?” (Needdefaultdict(lambda: defaultdict(int))for nested. Or use a recursive defaultdict factory.)
52. Itertools Module — Efficient Iteration Patterns
52. Itertools Module — Efficient Iteration Patterns
itertools.groupby requires the input to be sorted by the grouping key. If it is not sorted, you get wrong groups. Use defaultdict(list) for unsorted grouping.Red flag answer: Writing nested loops for cartesian products or manual accumulation when itertools has optimized versions.Follow-up:- “What’s the difference between
itertools.groupbyand SQLGROUP BY?” (itertools only groups consecutive equal elements. SQL groups all matching elements regardless of order.) - “How would you implement a sliding window using itertools?” (
itertools.islice+collections.dequeor themore_itertools.windowedrecipe.) - “What’s
itertools.starmapand when is it more readable thanmap?”
53. Functools Module — Higher-Order Function Utilities
53. Functools Module — Higher-Order Function Utilities
cached_property (3.8+):lru_cache exists. Or not knowing partial for configuration injection.Follow-up:- “What are the memory implications of
lru_cachewith large arguments?” (It holds strong references to all arguments as dict keys. Large objects will not be garbage collected.) - “How do you clear an
lru_cache?” (func.cache_clear(). Important for testing and memory management.) - “When would you use
reduceover a simple loop?” (Rarely in modern Python. Loops are more readable. Guido wanted to removereducefrom builtins.)
54. Exception Handling — Beyond try/except
54. Exception Handling — Beyond try/except
except Exception everywhere.- Catch specific exceptions, never bare
except:orexcept Exception:in production code. - Use
elseclause to separate “normal flow” from “error handling” — code inelseonly runs iftrysucceeded. - Re-raise or chain unknown exceptions.
except Exception: passis the worst anti-pattern. - Use
finallyfor cleanup or better, use context managers. - Exception groups (Python 3.11+):
except* ValueErrorfor handling multiple simultaneous exceptions fromTaskGroup.
except Exception: pass — silently swallowing all errors. Or not knowing about else/finally semantics.Follow-up:- “What’s the difference between
raiseandraise einside an except block?” (raisepreserves the original traceback.raise eresets it to the current line.) - “When would you use
except*(exception groups) from Python 3.11+?” (When usingasyncio.TaskGroupwhere multiple tasks can fail simultaneously.) - “How does
contextlib.suppresscompare to try/except for ignoring specific exceptions?”
55. Regular Expressions — Pattern Matching in Python
55. Regular Expressions — Pattern Matching in Python
'hello' in text is 10x faster than re.search(r'hello', text). Use str.startswith(), str.endswith(), str.split() when possible.Red flag answer: Using regex for everything including simple in checks. Or not knowing about named groups and re.compile.Follow-up:- “How would you prevent ReDoS (Regular Expression Denial of Service) attacks?” (Use
re2library which guarantees linear time. Or set timeout. Avoid nested quantifiers like(a+)+.) - “What’s the difference between greedy and non-greedy matching?” (
.*is greedy (match as much as possible),.*?is non-greedy (match as little as possible).) - “When would you use
re.VERBOSEflag?” (For complex patterns — allows whitespace and comments for readability.)
56. Logging Module — Production Observability
56. Logging Module — Production Observability
print() debugging in production or have proper structured logging.structlog:- Use
__name__for logger names (creates hierarchy matching module structure). - Use JSON format in production (parseable by ELK, Datadog, Splunk).
- Never log sensitive data (passwords, tokens, PII).
- Use lazy formatting (
%s) not f-strings in log calls. - Include request IDs for distributed tracing.
print() in production code. Or logging with f-strings that evaluate expensive expressions at every call.Follow-up:- “How does Python’s logging hierarchy work? If you set
logging.getLogger('app.db')to WARNING, what happens tologging.getLogger('app.db.queries')messages?” (Child inherits parent level unless explicitly set.) - “What’s the difference between
logger.exception()andlogger.error()?” (exception()automatically includes the traceback from the current exception context.) - “How would you add a request ID to every log message in a Django/FastAPI app?” (Middleware sets a context variable, custom log filter or
structlogprocessor adds it.)
8. Python Advanced Level Questions
57. Metaclasses — Classes That Create Classes
57. Metaclasses — Classes That Create Classes
typeis the default metaclass.type('MyClass', (BaseClass,), {'method': func})creates a class.- Custom metaclasses intercept class creation to modify, validate, or register classes.
- Django ORM:
ModelBasemetaclass registers models, collects field definitions, creates database table mappings. - SQLAlchemy: Uses metaclasses for declarative model definition.
- Abstract Base Classes:
ABCMetais a metaclass that enforces@abstractmethodcontracts. - Pydantic v1: Used metaclasses for model creation (v2 switched to
__init_subclass__).
__init_subclass__ (Python 3.6+) or class decorators. “Metaclasses are deeper magic than 99% of users should ever worry about” — Tim Peters.type, classes, and instances. Or using metaclasses when __init_subclass__ or a decorator would suffice.Follow-up:- “What’s the difference between
__new__in a metaclass vs__new__in a regular class?” (Metaclass__new__creates the class object. Regular__new__creates an instance.) - “How does Django’s
ModelBasemetaclass work internally?” (It collects Field instances from the class namespace, moves them to_meta, creates the db table mapping, and registers the model.) - “When would you use
__init_subclass__instead of a metaclass?”
58. Descriptors — The Mechanism Behind Properties, Methods, and Slots
58. Descriptors — The Mechanism Behind Properties, Methods, and Slots
@property, @classmethod, @staticmethod, __slots__, and even regular method binding.A descriptor is any object that defines __get__, __set__, or __delete__. When such an object is a class attribute, Python invokes the descriptor protocol instead of normal attribute access.Two types:- Data descriptor: Defines
__set__or__delete__. Takes priority over instance__dict__. - Non-data descriptor: Only defines
__get__. Instance__dict__takes priority.
- Data descriptors on the class (and its MRO)
- Instance
__dict__ - Non-data descriptors on the class
@property works internally: It is a data descriptor with __get__, __set__, and __delete__.How methods work: Functions are non-data descriptors. func.__get__(obj, type) returns a bound method.Red flag answer: Not knowing that @property is implemented via descriptors. Or conflating descriptors with decorators.Follow-up:- “Why do data descriptors take priority over instance attributes, but non-data descriptors don’t?” (Design choice: data descriptors need to intercept writes to validate/transform. Non-data descriptors should allow instance attributes to shadow them.)
- “How would you implement
@classmethodfrom scratch using descriptors?” - “What does
__set_name__do and why was it added in Python 3.6?” (Auto-provides the attribute name to the descriptor, eliminating the need for redundantname = Validated('name')patterns.)
59. Type Hints and Generics — Modern Python Typing
59. Type Hints and Generics — Modern Python Typing
Any as the default type hint.Follow-up:- “What’s the difference between
TypeVar('T', bound=Base)andTypeVar('T', int, str)?” (Bound means T is a subclass of Base. Constrained means T is exactly int or str.) - “How does
ParamSpec(PEP 612) help with decorator typing?” (Captures the parameter signature of the wrapped function, enabling correct type checking through decorators.) - “When would you use
@overloadfrom typing?” (To give different return types based on input types for the type checker.)
60. Asyncio Tasks and Futures — Concurrency Primitives
60. Asyncio Tasks and Futures — Concurrency Primitives
- Coroutine: An
async deffunction call. Does nothing until awaited or scheduled. - Task: A coroutine wrapped for concurrent execution. Created by
asyncio.create_task(). Starts running immediately on the event loop. Is a subclass of Future. - Future: A low-level promise of a future result. You rarely create these directly. Tasks use them internally.
await coro() (sequential) and create_task(coro()) (concurrent).Follow-up:- “What happens to unfinished tasks when the event loop closes?” (They get cancelled. If you do not
awaitthem, you getRuntimeWarning: coroutine was never awaited.) - “How does
asyncio.wait()differ fromasyncio.gather()?” (waitreturns two sets: done and pending. Supportsreturn_when=FIRST_COMPLETEDfor racing pattern.) - “How would you implement a timeout for an async operation?” (
async with asyncio.timeout(5):in Python 3.11+, orawait asyncio.wait_for(coro(), timeout=5).)
61. Context Variables — Async-Safe Thread-Local Storage
61. Context Variables — Async-Safe Thread-Local Storage
threading.local()? In asyncio, multiple coroutines share a single thread. threading.local() would give ALL coroutines the SAME value. ContextVar gives each task its own copy.Production use cases:- Request ID propagation for distributed tracing
- Current user / tenant in multi-tenant applications
- Database transaction context
- Locale / timezone per request
threading.local() for request-scoped data in async code.Follow-up:- “How do ContextVars interact with
asyncio.create_task()?” (Tasks inherit a copy of the current context. Changes in the task do not affect the parent.) - “How does Starlette/FastAPI use ContextVars internally?”
- “What’s the
copy_context()function for?” (Creates a snapshot of current context that can be run in a different thread/task.)
62. Memory Views and Buffer Protocol — Zero-Copy Data Access
62. Memory Views and Buffer Protocol — Zero-Copy Data Access
- Processing large binary files (images, video, network packets)
- High-performance networking (receiving MB of data, slicing into messages)
- Scientific computing (NumPy arrays expose the buffer protocol)
- “What types support the buffer protocol?” (
bytes,bytearray,array.array,numpy.ndarray,mmapobjects,ctypesarrays.) - “How does
socket.recv_into()+ memoryview reduce memory allocations compared tosocket.recv()?” (recv()allocates a new bytes object each call.recv_into()writes to an existing buffer.) - “How does this relate to Python’s
structmodule for binary data parsing?”
63. Profiling with cProfile and Memory Profiling — Finding Bottlenecks
63. Profiling with cProfile and Memory Profiling — Finding Bottlenecks
- py-spy: Sampling profiler. Attaches to running process. No code changes needed. Low overhead.
- Pyroscope / Datadog APM: Continuous profiling in production.
- objgraph: Find memory leaks by tracing object references.
- Measure first, optimize second. Never guess.
- Profile in conditions similar to production (data size, concurrency).
- Look at cumulative time (the hot path), not just self time.
- Optimize the top bottleneck, then re-profile. Repeat.
- “How would you profile a running production process without restarting it?” (
py-spy top --pid 12345orpy-spy record --pid 12345 -o profile.svgfor flame graphs.) - “What’s the difference between
cProfile(deterministic) andpy-spy(sampling)?” (cProfile hooks into every function call — high overhead. py-spy samples the stack periodically — low overhead, suitable for production.) - “How do you find memory leaks in a long-running Python service?” (Take
tracemallocsnapshots at intervals, compare them. Or useobjgraph.show_growth()to find types with increasing counts.)
64. Protocol Classes (Structural Typing) — Duck Typing with Type Safety
64. Protocol Classes (Structural Typing) — Duck Typing with Type Safety
| Aspect | Protocol | ABC |
|---|---|---|
| Type checking | Structural (has the right methods) | Nominal (inherits from the ABC) |
| Runtime enforcement | Optional (@runtime_checkable) | Always (TypeError on instantiation) |
| Third-party classes | Can satisfy without modification | Must be subclassed or registered |
| Best for | Library APIs, loose coupling | Plugin systems, strict contracts |
- “Can a Protocol have default implementations?” (Yes, but then classes must explicitly inherit from it to get the defaults. This defeats the structural typing advantage.)
- “How do Protocols compare to Go’s interfaces?” (Very similar — both are structural. Go’s are implicit, Python’s are checked by tools like mypy.)
- “What’s the overhead of
@runtime_checkableand when should you avoid it?” (It usesisinstancechecks which inspect the class MRO. Acceptable for occasional checks, not in hot loops.)
65. AST Manipulation — Code as Data
65. AST Manipulation — Code as Data
- Linters (flake8, pylint): Parse AST to detect code smells, unused imports, complexity.
- Code formatters (Black): Parse AST, format, verify AST is unchanged (semantic equivalence).
- Coverage tools: Instrument AST to track which branches execute.
- Security scanners (Bandit): Detect dangerous patterns (e.g.,
eval(),pickle.loads()with untrusted input). - Macro systems (MacroPy): Extend Python’s syntax via AST transformation at import time.
- “How does
ast.literal_eval()differ fromeval()and why is it safer?” (literal_evalonly evaluates literals (strings, numbers, tuples, lists, dicts, booleans, None). Cannot execute arbitrary code.) - “How does Black ensure it doesn’t change code behavior when reformatting?” (It compares the AST before and after formatting. If the ASTs differ, it refuses to format.)
- “How would you write a custom linting rule that detects
print()statements in production code?“
9. Advanced Patterns & Production Python
66. The GIL-Free Future: Python 3.13+ Free-Threading
66. The GIL-Free Future: Python 3.13+ Free-Threading
--disable-gil / PYTHON_GIL=0). Python 3.14+ stabilizes it further. This removes the Global Interpreter Lock, allowing true parallel execution of Python threads.What changes:- CPU-bound multi-threaded Python code can now use all CPU cores.
- Reference counting becomes thread-safe via biased reference counting (fast path for the owning thread, atomic operations for cross-thread access).
- Container operations use per-object locks instead of the GIL.
threading.Threadcan achieve true parallelism for the first time.
- Asyncio still works the same way (single-threaded cooperative multitasking).
- Multiprocessing still works the same way.
- You still need locks for shared mutable state (the GIL was never a substitute for proper synchronization).
- C extensions must be updated for thread safety. NumPy, pandas, and major libraries are actively working on this.
- Code that accidentally relied on the GIL for thread safety will break.
- Performance of single-threaded code is ~5-10% slower due to thread-safety overhead.
- “What code that works today might break with free-threading?” (Code that mutates shared data structures from multiple threads without locks, relying on the GIL for implicit synchronization.)
- “How does biased reference counting work?” (Each object has an owner thread that uses fast non-atomic refcount operations. Other threads use slower atomic operations. When the owner thread changes, ownership is transferred.)
- “Should you rewrite your multiprocessing code to use threading now?” (Not immediately. Free-threading is experimental, C extension compatibility is incomplete, and multiprocessing’s process isolation has advantages for fault tolerance.)
67. Pattern Matching (match/case) — Python 3.10+
67. Pattern Matching (match/case) — Python 3.10+
match or Scala’s pattern matching.Red flag answer: “It’s just Python’s version of switch/case.” This misses the structural matching, destructuring, and guard clause capabilities.Follow-up:- “How does pattern matching interact with custom classes? What’s
__match_args__?” (Classes can define__match_args__to specify which positional patterns map to which attributes. Dataclasses set this automatically.) - “When would you choose pattern matching over if/elif chains?” (When you are destructuring data — parsing API responses, handling message types, processing AST nodes.)
- “Can you use pattern matching with type narrowing in mypy?” (Limited support. mypy is still catching up with match/case type narrowing.)
68. Pydantic v2 — Data Validation at Production Scale
68. Pydantic v2 — Data Validation at Production Scale
- Pydantic: Runtime validation, serialization, JSON schema generation. Best for API boundaries.
- Dataclasses: No validation overhead, faster instantiation. Best for internal data.
- “How does Pydantic v2’s Rust core achieve 5-50x speedup over v1?” (Core validation logic written in Rust, compiled as a Python C extension. Avoids Python-level overhead for type checking and coercion.)
- “When would you use Pydantic’s
strict=Truemode?” (When you want exact types — no coercion."42"would NOT be accepted for anintfield. Good for internal APIs where you control the caller.) - “How does Pydantic’s
model_configfrom_attributes=Truework for ORM integration?” (Allows creating Pydantic models from ORM objects like SQLAlchemy models by reading attributes instead of dict keys.)
69. Celery and Task Queues — Background Processing at Scale
69. Celery and Task Queues — Background Processing at Scale
- Sending emails/notifications
- Image/video processing
- PDF generation
- Data import/export
- Periodic jobs (cron replacement with Celery Beat)
- Any work taking >500ms that should not block the HTTP response
- Idempotency: Tasks may execute more than once (at-least-once delivery). Make them idempotent.
- Visibility timeout: If a worker crashes, the task is re-queued after the timeout. If your task takes 30 min, set the timeout accordingly.
- Prefetch limit: Controls how many tasks a worker grabs at once. Set to 1 for long-running tasks to avoid starvation.
- Monitoring: Use Flower for real-time monitoring. Track queue depth, task latency, failure rate.
- Serializer: Use JSON (not pickle!) for security. Celery’s default changed from pickle to JSON.
threading for background work in my web app.” This does not survive process restarts, cannot distribute across machines, and loses tasks on crashes.Follow-up:- “What happens if a Celery worker crashes mid-task?” (The task becomes invisible for the visibility timeout period, then reappears in the queue. Another worker picks it up. This is why idempotency matters.)
- “How would you handle a task that must run exactly once?” (Extremely difficult in distributed systems. Use idempotency keys + database constraints. “Exactly once” is technically impossible in distributed systems — aim for “effectively once” via idempotent operations.)
- “When would you choose Dramatiq or Huey over Celery?” (Dramatiq: simpler API, better defaults, built-in rate limiting. Huey: lightweight, good for small projects. Celery: most mature, largest ecosystem, best for complex workflows.)
70. Python Packaging and Dependency Management — The Modern Stack
70. Python Packaging and Dependency Management — The Modern Stack
pip install things globally.The modern stack (2024+):pyproject.toml: Single config file replacingsetup.py,setup.cfg,MANIFEST.in. PEP 621 standard.uv: Extremely fast package installer and resolver (written in Rust by Astral). 10-100x faster than pip.ruff: Extremely fast linter + formatter (also Rust/Astral). Replaces flake8, isort, Black, pyflakes.
| Tool | Lock file | Speed | Virtual env |
|---|---|---|---|
| pip + venv | requirements.txt (manual) | Slow | Manual |
| Poetry | poetry.lock (auto) | Medium | Built-in |
| PDM | pdm.lock (auto) | Medium | PEP 582 support |
| uv | uv.lock (auto) | Very fast | Built-in |
pip install into global Python. No virtual environment. No lock file. No pyproject.toml.Follow-up:- “What’s the difference between
requirements.txtand a lock file likepoetry.lock?” (Lock file pins exact versions of ALL dependencies including transitive ones.requirements.txttypically only pins direct dependencies unless you runpip freeze.) - “How would you handle a dependency conflict where library A needs
X>=2.0and library B needsX<2.0?” (Dependency resolution failure. Options: find compatible versions, fork one library, use separate virtual environments, or contact maintainers.) - “What’s
uvand why is it replacing pip in many workflows?” (Written in Rust, 10-100x faster than pip for resolution and installation. Drop-in replacement with better UX.)
71. Testing Best Practices — pytest and Beyond
71. Testing Best Practices — pytest and Beyond
- Unit tests (70%): Fast, isolated, test single functions. Mock external dependencies.
- Integration tests (20%): Test component interactions. Real database, real cache.
- E2E tests (10%): Full system. Slow, expensive, but catch integration issues.
- Test behavior, not implementation. Tests should not break when you refactor internals.
- One assertion per concept (not necessarily one per test).
- Use factories (
factory_boy) instead of fixtures for complex test data. - Measure coverage but do not worship it. 80% coverage with meaningful tests beats 100% with trivial ones.
- “How do you test code that calls external APIs without hitting the real API?” (Mock at the HTTP level with
responsesorhttpx.MockTransport, or mock the client at the function level.) - “What’s the difference between
unittest.mock.patchtarget syntax andmonkeypatch?” (pytest’smonkeypatchis scoped to the test automatically.patchrequires explicit context management or decorator.) - “How do you handle flaky tests in CI?” (Mark with
@pytest.mark.flaky(reruns=3), but also investigate the root cause. Common causes: time-dependent logic, shared state, network calls.)