Why the Global Interpreter Lock?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The Global Interpreter Lock, or GIL, is one of the main reasons Python threading behaves differently from threading in many other languages. It exists in CPython to protect the interpreter's internal state, especially around object management, and the tradeoff is that CPU-bound Python threads do not run Python bytecode truly in parallel.
What the GIL Actually Does
In CPython, the GIL is a process-wide lock that allows only one thread at a time to execute Python bytecode. Multiple threads can exist, block on I/O, sleep, and wait, but when it comes to executing Python instructions inside the interpreter, only one thread holds the lock at a given moment.
That is why people say "Python threads do not run in parallel." More precisely, Python threads in CPython do not execute Python bytecode in parallel. That distinction matters, because I/O-bound threading can still be very useful.
Why CPython Has the GIL
The short answer is implementation simplicity and performance tradeoffs.
CPython has historically relied heavily on reference counting for memory management. Every Python object tracks how many references point to it. When the count reaches zero, the object can be freed. Without a lock, those reference count updates would need much more synchronization, which would complicate the interpreter and add overhead to many common operations.
The GIL makes object model invariants easier to maintain and keeps the single-threaded case relatively fast. That was a very practical tradeoff for a language whose popularity grew around ease of use, extension modules, and predictable C API behavior.
Why It Hurts CPU-Bound Threads
If two Python threads both spend their time doing CPU work, they compete for the GIL instead of using two cores efficiently. A simple example demonstrates the idea:
This uses two threads, but in CPython it does not give true parallel bytecode execution across cores. The GIL forces those threads to take turns executing Python-level work.
Why I/O-Bound Threads Still Help
The GIL is much less painful for programs waiting on the network, disk, or sockets. During many blocking I/O operations, the interpreter can release the GIL so another thread can run.
That means threads still work well for tasks such as:
- downloading many URLs
- waiting on database responses
- handling socket-based servers
- performing mixed I/O and light CPU work
The limitation is mainly about CPU-bound Python execution, not about all concurrency.
Common Workarounds
When CPU parallelism matters, Python developers usually choose one of these approaches:
- '
multiprocessingto use separate processes and separate interpreters' - native extensions that release the GIL during heavy work
- vectorized libraries such as NumPy that do much of the real work in native code
- '
asynciofor high-concurrency I/O workloads without many threads'
Here is a simple multiprocessing example:
Because separate processes have separate interpreters, they do not share one GIL.
Why Python Has Not Simply Removed It
Removing the GIL is not just deleting one lock. It changes memory management costs, extension-module assumptions, C API behavior, and performance characteristics across the ecosystem.
That is why the question "why not just remove it" has always had a complicated answer. A no-GIL interpreter can be built, but making it fast, compatible, stable, and broadly ecosystem-friendly is much harder than the slogan suggests.
Common Pitfalls
The biggest mistake is assuming threads are useless in Python. They are often quite effective for I/O-bound workloads.
Another issue is expecting a CPU-bound thread pool to scale linearly across cores in CPython. The GIL prevents that at the Python bytecode level.
Developers also confuse "Python has a GIL" with "all Python implementations behave the same way." The GIL discussion is mainly about CPython.
Summary
- The GIL allows only one CPython thread at a time to execute Python bytecode.
- It exists largely to simplify interpreter internals and object memory management.
- CPU-bound Python threads do not get true parallel bytecode execution.
- I/O-bound threading can still work well because the GIL is often released during blocking operations.
- For CPU parallelism, use processes, native code that releases the GIL, or other concurrency strategies.

