Senior Concurrency 7 min read

Python GIL Explained — What It Is and How to Work Around It

The Interview Question

What is the GIL in Python? How does it affect multithreading and what are the workarounds?

Expert Answer

The Global Interpreter Lock is a mutex in CPython that allows only one thread to execute Python bytecode at a time. Even on a multi-core machine with multiple threads, only one thread runs Python code at any given moment. The GIL exists because CPython's memory management (reference counting) is not thread-safe — the GIL is the simplest way to prevent race conditions on reference counts. The impact: CPU-bound multithreaded Python code gets zero parallelism. A computation split across 4 threads on 4 cores runs at roughly the same speed as a single thread — sometimes slower due to lock contention. However, the GIL is released during I/O operations (network requests, file reads, database queries). This means threading IS effective for I/O-bound workloads — while one thread waits for a network response, another can execute. For CPU-bound parallelism, the workarounds are: multiprocessing (separate processes each with their own GIL), C extensions (NumPy releases the GIL during array operations), and asyncio (cooperative multitasking for I/O-bound work without threads).

Key Points to Hit in Your Answer

  • GIL = only one thread executes Python bytecode at a time
  • Exists because CPython's reference counting isn't thread-safe
  • Threading works for I/O-bound tasks (GIL released during I/O)
  • Threading does NOT help CPU-bound tasks (GIL prevents true parallelism)
  • multiprocessing module bypasses GIL with separate processes
  • NumPy, pandas operations release GIL during C-level computation
  • Python 3.13+ has experimental free-threaded mode (no-GIL build)
  • asyncio is single-threaded concurrency — no GIL issues by design

Code Example

import multiprocessing
import asyncio
import aiohttp

# CPU-bound: use multiprocessing (not threading)
def cpu_heavy(n):
    return sum(i * i for i in range(n))

# This actually uses multiple cores
with multiprocessing.Pool(4) as pool:
    results = pool.map(cpu_heavy, [10**7] * 4)

# I/O-bound: threading works fine (GIL released during I/O)
# But asyncio is more efficient for many connections
async def fetch_all(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [session.get(url) for url in urls]
        return await asyncio.gather(*tasks)

# 1000 concurrent requests, single thread, no GIL issue
results = asyncio.run(fetch_all(urls))

What Interviewers Are Really Looking For

Senior-level question. They want you to know the GIL exists, why it exists (reference counting), and the practical implications. The strongest answer distinguishes I/O-bound (threading fine) from CPU-bound (need multiprocessing). Mentioning Python 3.13's experimental no-GIL build shows you follow language development. Don't trash the GIL — explain the tradeoff it makes.

Practice This Question with AI Grading

Reading about interview questions is a start — but practicing with real-time AI feedback is how you actually get better. Goliath Prep grades your answers instantly and tells you exactly what you're missing.

Start Practicing Free →