1. GIL — the headline
- GIL (Global Interpreter Lock): a single lock allowing only one thread to execute Python bytecode at a time.
- Consequence: pure-Python threads do not run in parallel on multiple cores.
- Where it doesn’t matter: I/O-bound work (the interpreter releases GIL during blocking syscalls) and heavy C/Python extensions that release the GIL (numpy, etc.).
- Where it hurts: CPU-bound pure-Python threads → no speedup across cores.
2. Threads vs Processes
threading | multiprocessing | |
|---|---|---|
| Concurrency | yes | yes |
| Parallelism | GIL-limited | yes (separate interpreters) |
| Memory | shared | separate (pickled IPC) |
| Best for | I/O-bound | CPU-bound |
| Cost | one process | process spawn overhead |
multiprocessing.Poolmaps work across cores:Pool(4).map(func, data).concurrent.futures: unified API —ThreadPoolExecutorvsProcessPoolExecutorwithFutures.
Gotcha on shared state: multiprocessing copies memory — a change in a child isn’t visible in the parent unless you use Queue/Pipe/Value/Array.
3. async / await
asyncio— single-threaded, cooperative multitasking for I/O-bound code.async defcoroutine,awaitsuspends on blocking I/O,asyncio.run(...)drives the loop.- Tasks run concurrently, not in parallel; no preemption — blocking code (e.g.,
time.sleep, CPU work) blocks the whole loop.
import asyncio
async def ping(name):
await asyncio.sleep(0.1)
return name
async def main():
results = await asyncio.gather(ping("a"), ping("b"))
# concurrent; not parallel
- asyncio vs threads: async/await — no OS threads/locks; but requires non-blocking code throughout. Threads are preemptive; async is cooperative.
4. Thread safety rules
- Python +
+=/x = x+1is not atomic — read-modify-write can be interleaved. - Use
threading.Lockaround shared mutations. queue.Queueis thread-safe for producer-consumer.threading.local()for per-thread storage.
5. The gotcha sheet (interview gold)
- Mutable default args — shared across calls (
def f(x=[])). - Late binding closures / lambdas — loop var captured by reference.
isvs==/ small-int caching —256 is 257False,257False by identity.- Integer division —
-7 // 2=-4;-7 % 3=2. - String + int — concatenation raises
TypeError; conversion required. - Chained assignment —
a = b = []shares the same list. - Boolean trap —
bool([])isFalse;bool(0)isFalse. - Copy vs deepcopy — shallow share.
tupleunhashable inner list —hash((1, []))→ TypeError.returnin a generator —StopIterationvsreturntriggering.- String repetition vs addition —
"ab" * 3→"ababab";"ab" + 3→TypeError. sortedreturns a new list;.sort()sorts in place — know which one you’re calling.rangevslist— lazy vs eager.raisevsassert— production raising vs debug-only assertion.__init__vs__new__— initializer vs constructor.
6. Performance quick hits
- Favor built-ins and list-comprehensions over hand loops (C-accelerated).
if x in setvsin list— O(1) vs O(n).- Generator expressions for large one-shot pipelines.
- Profile first — don’t micro-opt (the story is in
cProfile/timeit).
7. Final interview patterns
Python interviews reward fundamentals: name the GIL, nail is vs ==, mutable defaults, and if __name__ == "__main__". Be ready for a time complexity question (list vs set/dict membership) and one asyncio overview.
Premium Content
Unlock Part 5: Concurrency & Top Gotchas and all premium lessons with a subscription.
All premium lessons
Ad-free experience
Priority support
From ₹199.99/year — See plans