Python — Theory
Python — Theory (interview deep-dive)
Section titled “Python — Theory (interview deep-dive)”The GIL
Section titled “The GIL”The Global Interpreter Lock is a mutex protecting CPython object memory. Only one thread executes Python bytecode at a time.
- Why exists: simplifies CPython’s reference-counting GC. Removing it requires per-object locks or alternative GC (PyPy uses generational without GIL).
- Released during: I/O (network, file),
time.sleep, blocking C extensions that explicitly release it (numpy ops). So threads still help I/O-bound work. - Hurts CPU-bound multi-threading — threads can’t use multiple cores for pure Python compute.
- Workarounds:
multiprocessing(each process has own GIL/heap; IPC via pipe/queue/shm).- C extensions (numpy, cython) that release GIL.
- Free-threaded Python 3.13 (PEP 703) — experimental, no GIL.
asynciofor I/O concurrency on single thread.
asyncio model
Section titled “asyncio model”- Single thread, single event loop, cooperative multitasking.
awaitsuspends the coroutine; loop schedules others.- Event loop never knows what’s blocking — if you call sync I/O inside coroutine, loop freezes.
- Tasks vs coroutines:
create_taskschedules immediately; bare coroutine is just an object. - Cancellation:
task.cancel()raisesCancelledErrorat nextawait. Must handle for cleanup. - Context propagation:
contextvars.ContextVarsurvives acrossawait.
Memory model
Section titled “Memory model”- Reference counting (primary GC): every object has refcount. When 0 → freed immediately.
- Cyclic GC (secondary): handles ref cycles (A → B → A). Generational (3 generations).
sys.getsizeof(obj)reports shallow size.- Memory leaks: usually unintentional global refs, caches, listeners, circular refs in C extensions.
- Diagnose:
tracemalloc,objgraph,memory_profiler,pympler.
Common interview questions
Section titled “Common interview questions”-
What does
@propertydo, and how does it interact with inheritance? Descriptor that turns method into attribute access. Can have getter, setter, deleter. -
__slots__tradeoffs? Memory savings (no per-instance dict). Cost: no dynamic attrs, harder to subclass without slots conflict, can’t useweakrefunless added explicitly. -
Decorators with args: 3-level nesting.
def tag(name):def deco(fn):def wrap(*a, **k): print(name); return fn(*a,**k)return wrapreturn deco -
Mutable default args bug:
def f(x=[]): # shared across calls!x.append(1); return x# use: def f(x=None): x = x or [] -
Generator vs iterator: every generator is an iterator; not all iterators are generators. Generators are easier to write (
yield). -
Metaclasses: class of a class.
typeis the default. Override__init__/__new__of metaclass to customize class creation. Used by Django ORM, SQLAlchemy declarative base, ABCs. -
async defvsdefreturning Future: coroutine objects are awaitable; sync function returning Future is also awaitable.
Django specifics
Section titled “Django specifics”- Request lifecycle: WSGI/ASGI handler → middleware (request) → URL resolver → view → middleware (response).
- ORM N+1:
select_related('fk_field')→ SQL JOIN, eager fetch (FK and OneToOne).prefetch_related('m2m')→ 2 queries withIN, joined in Python (M2M and reverse FK). - Lazy querysets evaluate on iteration, slicing (without step),
len,bool,list(),repr(in shell). - Migrations:
makemigrationswrites file,migrateapplies. Squash old migrations periodically. - Signals: avoid for new code — implicit, hard to test. Prefer explicit calls / domain events.
select_for_update: row lock for transaction. Combine withtransaction.atomic().- Channels for WebSockets/ASGI.
FastAPI specifics
Section titled “FastAPI specifics”- Built on Starlette + Pydantic.
- Dependency injection via
Depends(fn)— testable, composable. - Background tasks via
BackgroundTasksfor fire-and-forget after response. - Pydantic v2: 5-50× faster than v1 (Rust core).
model_validate,model_dump.
Performance & profiling
Section titled “Performance & profiling”cProfile+snakevizto visualize.py-spy— sampling profiler, no code change, attaches to running process.line_profilerfor hot functions.- Common wins: avoid
+string concat in loops (use''.join), usedict/setfor membership, cache withfunctools.lru_cache, use generators for streaming, use C-backed libs (orjson, ujson, cython).
Common pitfalls
Section titled “Common pitfalls”- Late binding in closures:
[lambda: i for i in range(3)]— all return 2. Fix:[lambda i=i: i ...]. - Modifying a list while iterating.
- Catching too broadly (
except Exceptionsilently swallowing). - Using
isfor value comparison (x is 1000may be False; CPython interns small ints only). - Forgetting
__hash__when overriding__eq__→ object unusable in dict/set.