Tech_Interview_Prep

Concurrency (GIL, Threading, Asyncio)

Why Python threads don't parallelize CPU work, and the two real ways around it: multiprocessing and asyncio.

Try answering in your head first, then click a question to check the model answer.

Q1.Why doesn't Python's `threading` module give you true parallelism for CPU-bound work?(show answer)

The GIL allows only one thread to execute Python bytecode at a time, regardless of how many CPU cores are available — so CPU-bound threads take turns rather than running simultaneously. For CPU-bound parallelism, multiprocessing is needed instead, since each process gets its own Python interpreter and GIL.

Q2.When is `threading` still genuinely useful in Python, given the GIL?(show answer)

For I/O-bound work — network requests, file/database I/O — where a thread spends most of its time waiting rather than executing Python bytecode. The GIL is released during blocking I/O calls, so other threads can run during that wait, giving real concurrency benefit even though CPU-bound code can't run in true parallel.

Q3.How does `asyncio` achieve concurrency differently from `threading`?(show answer)

asyncio uses a single thread and an event loop that cooperatively switches between tasks at await points — a task voluntarily yields control while waiting on I/O, letting the loop run other tasks in the meantime. This avoids thread-switching overhead and race conditions from preemptive context switches, but it only works if the code actually awaits non-blocking operations; a blocking call in async code stalls the entire event loop.

Q4.For a task that's both I/O-bound and CPU-bound in parts, how would you combine these concurrency models?(show answer)

Use asyncio (or threads) for the I/O-bound parts to overlap waiting time, and offload the CPU-bound parts to a multiprocessing pool (or loop.run_in_executor with a process pool from async code) so the heavy computation runs on separate cores in parallel rather than blocking the event loop or being serialized by the GIL.