Skip to the solution
MediumDSA15 min read

What is the Global Interpreter Lock (GIL)?

1.1k views

The problem

Question presented to candidate: "What is the GIL in Python? Why does CPython have one, what does it mean for multithreaded programs, and if it only lets one thread run at a time, why do we still need locks?"

What a strong answer should cover:

  • The GIL is a mutex inside CPython: only the thread holding it may execute Python bytecode, so one interpreter runs at most one thread of Python code at a time, even on many cores
  • Why it exists: CPython's memory management (reference counts on every object) and much of its internals and C-extension ecosystem are not thread-safe; one global lock made them safe simply and keeps single-threaded code fast
  • When it is released: on blocking I/O and time.sleep, inside C code that opts out (Py_BEGIN_ALLOW_THREADS, used by many I/O and number-crunching libraries), and every switch interval (5 ms by default, sys.getswitchinterval()) so other threads get a turn
  • Consequence: threads speed up I/O-bound work but not CPU-bound pure-Python work; use multiprocessing/ProcessPoolExecutor or C extensions for CPU parallelism
  • The GIL does not make your code thread-safe: counter += 1 is several bytecodes and a switch can land between them, so shared state still needs threading.Lock
  • Where it is going: per-interpreter GILs in 3.12 (PEP 684), an optional free-threaded build in 3.13 (PEP 703, experimental) that became officially supported in 3.14 (PEP 779)

Clarifying questions expected:

  • Is the workload CPU-bound or I/O-bound?
  • Which Python version and build (standard or free-threaded) will it run on?

Code / implementation expected: Optional β€” showing that counter += 1 is several bytecodes, or contrasting CPU-bound and I/O-bound threads.

  • gil
  • concurrency

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

15 min read Β· 12 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

PythonWhy the GIL exists and why += still races (simulated)
PythonReal threads: CPU-bound vs I/O-bound, and a lost-update race
import sys
import threading
import time

# Run on a normal (GIL) CPython 3.12 build. Threads cannot start in
# the browser build of Python, so this example is shown as code.
print("GIL build:", not hasattr(sys, "_is_gil_enabled") or sys._is_gil_enabled())

def cpu_work(n=3_000_000):
    total = 0
    for i in range(n):
        total += i
    return total

def timed(fn):
    start = time.perf_counter()
    fn()
    return time.perf_counter() - start

def serial_cpu():
    for _ in range(4):
        cpu_work()

def threaded(target):
    def go():
        threads = [threading.Thread(target=target) for _ in range(4)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()
    return go

# CPU-bound: four threads take about as long as doing the work in a row
t_serial = timed(serial_cpu)
t_threads = timed(threaded(cpu_work))
print("CPU-bound, 4 threads at least 2x faster:", t_threads < t_serial / 2)

# I/O-bound: sleeping (like waiting on a socket) releases the GIL,
# so four 0.2 s waits overlap
def wait_io():
    time.sleep(0.2)

t_io = timed(threaded(wait_io))
print("I/O-bound, 4 x 0.2 s waits finished in under 0.4 s:", t_io < 0.4)

# The GIL does not make your code thread-safe: a read, a switch,
# then a write loses updates
counter = 0
def unsafe_add():
    global counter
    for _ in range(1000):
        value = counter
        time.sleep(0)        # gives the GIL away, like a switch would
        counter = value + 1

threaded(unsafe_add)()
print("without a lock, all 4000 updates kept:", counter == 4000)

counter = 0
lock = threading.Lock()
def safe_add():
    global counter
    for _ in range(1000):
        with lock:
            value = counter
            time.sleep(0)
            counter = value + 1

threaded(safe_add)()
print("with a lock, counter =", counter)

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 45 of 139 in Python

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Python questions
Up nextMedium

How does Polars LazyFrame query optimization work?

Continue