Skip to content
Languages

Python GIL Removal Explained: Free-Threaded Python in 3.13, 3.14 and 3.15

The Python GIL is not gone, but it is no longer mandatory. Python 3.13 shipped an optional free-threaded build, Python 3.14 made that build officially supported, and Python 3.15, released on 9 October 2026, keeps it optional. This guide explains what the lock protected, what the phased plan actually promises, how to check which interpreter you are running (with exact output from two local builds), and how to answer the interview question about CPU-bound parallelism without overstating the change.

By Published

9 min readEditorial analysisUpdated
  • Python
  • GIL
  • free threading
  • concurrency
  • PEP 703
What to remember

Free threading removes the GIL only in a separate, optional build, and only while no unported extension turns it back on. Check sys._is_gil_enabled() at runtime, keep your own locks, and pick processes, subinterpreters or threads based on the workload and the build you actually run.

What the GIL protects and why it lasted so long

The Python glossary defines the global interpreter lock as the mechanism that lets only one thread execute Python bytecode at a time. That single rule makes the object model, including built-in types such as dict, implicitly safe against concurrent access, and it keeps reference counting cheap: object counts can change without atomic instructions. Decades of C extensions also lean on that guarantee, which is the main reason removing the lock took a multi-release plan rather than a patch.

The GIL never made threads useless. It is always released during blocking I/O, and extensions can release it during heavy work such as compression or hashing, so a thread pool that waits on sockets or calls into such code still overlaps work. What it prevents is parallel execution of pure Python bytecode: four threads running a CPU-bound loop on the default build take turns. PEP 703 lists the usual workarounds, multiprocessing or rewriting hot paths in C++, and argues that both add memory, communication and maintenance costs.

PEP 703, PEP 779 and the phased rollout

PEP 703, by Sam Gross, proposed a --disable-gil build of CPython. The Steering Council accepted it on 24 October 2023 on the condition that the rollout be gradual and reversible. The build replaces what the GIL used to guarantee with several mechanisms: biased reference counting (the owning thread updates a local count without atomics), immortal objects whose counts never change, deferred reference counting for widely shared objects, the thread-safe mimalloc allocator, and lightweight per-object locks on lists, dicts and sets.

Python 3.13 shipped the build as experimental under a separate executable, usually python3.13t. PEP 779 then defined the bar for phase II, including a hard 15 percent target for single-thread overhead and a 20 percent target for memory overhead, and Python 3.14 declared the free-threaded build supported but still optional. Python 3.15 adds a Stable ABI for free-threaded builds (abi3t, PEP 803), so one extension build can serve free-threaded 3.15 and later, although porting to it usually needs source changes. Phase III, making free threading the default or only build, is explicitly undecided.

Free-threaded CPython status by release, as of November 2026
ReleaseFree-threaded buildWhat to know
3.13 (Oct 2024)ExperimentalSeparate python3.13t binary; expect bugs
3.14 (Oct 2025)Supported, optionalPEP 779 phase II; default build keeps the GIL
3.15 (Oct 2026)Supported, optionalabi3t Stable ABI for extensions (PEP 803)
Phase IIIUndecidedDefault or sole build needs a future decision

Check which build you are running

Two checks answer two different questions. sysconfig.get_config_var("Py_GIL_DISABLED") reports how the interpreter was compiled: 1 for a free-threaded build, 0 for the default build. sys._is_gil_enabled(), added in 3.13, reports whether the GIL is active in this process right now, which matters because a free-threaded build can turn it back on. The script below guards the second call so it also runs on older versions.

On Homebrew's Python 3.14.2 it printed the version line, then Py_GIL_DISABLED = 0 and GIL active: True: a default build. On pyenv's Python 3.12.8 the config variable printed None and the guard printed the unknown message, because neither exists before 3.13. Asking the 3.14 default build to drop the lock with -X gil=0 or PYTHON_GIL=0 does not degrade gracefully; it exits with Fatal Python error: config_read_gil: Disabling the GIL is not supported by this build. On a free-threaded build, python -VV includes the words free-threading build.

import sys
import sysconfig

print(sys.version)
print("Py_GIL_DISABLED =", sysconfig.get_config_var("Py_GIL_DISABLED"))
check = getattr(sys, "_is_gil_enabled", None)
print("GIL active:", check() if check else "unknown (no sys._is_gil_enabled)")

What free threading changes for your code

Thread safety stays your job. The free-threading HOWTO says built-in types such as dict, list and set use internal locks that behave similarly to the GIL, yet it still recommends threading.Lock and other primitives instead of relying on those internals. Some things are explicitly unsafe: sharing one iterator between threads can yield duplicate or missing elements, and reading frame.f_locals of a frame running in another thread can crash the interpreter. Code that only looked safe because bytecode ran one thread at a time deserves a review.

Extensions decide more than your own code does. A C extension must declare support, for example with the Py_mod_gil slot set to Py_MOD_GIL_NOT_USED; importing one that does not makes a free-threaded interpreter re-enable the GIL and print a warning. PYTHON_GIL=0 or -X gil=0 forces the lock off anyway, at your own risk. Free-threaded wheels carry a t suffix in their ABI tag, such as cp315t, and the docs point to community trackers of which popular packages ship them.

  • Run the test suite on the free-threaded binary with the GIL off, then with PYTHON_GIL=1, and compare failures.
  • Treat the re-enable warning at import as a failing check: one unported extension brings the GIL back for the whole process.
  • Keep explicit locks around compound updates such as check-then-set on a shared dict.
  • Expect higher memory use; the HOWTO cites immortal interned strings, larger object headers and delayed frees.

Single-thread overhead: what the official docs say

Free threading is not free for single-threaded code. The What's New for Python 3.14 puts the penalty at roughly 5 to 10 percent depending on platform and C compiler, a release in which the specializing adaptive interpreter was enabled in free-threaded mode. The free-threading HOWTO gives a pyperformance average ranging from about 1 percent on macOS aarch64 to 8 percent on x86-64 Linux, and stresses that the figure depends on workload and hardware. These are the project's published numbers; this article ran no benchmarks.

Memory is the other cost. PEP 779 reported memory overhead of about 15 to 20 percent when it was written and set 20 percent as the phase II target. For an interview, the honest summary is that a free-threaded interpreter pays a measurable tax on one thread to gain real parallelism on many. It wins when CPU-bound work splits cleanly across threads and every dependency supports it. Measure your own workload on both builds before switching production.

Interview answer: CPU-bound parallelism in Python

A strong answer starts by naming the bottleneck. I/O-bound work runs well with asyncio or threads on any build, because waiting releases the GIL. CPU-bound pure Python on the default build needs separate interpreters: ProcessPoolExecutor side-steps the GIL with processes, and 3.14 adds InterpreterPoolExecutor, where each worker has its own interpreter and its own GIL. Both pickle arguments and results, so send coarse chunks. If the hot loop runs inside an extension that releases the GIL, plain threads may already scale.

Then add the free-threaded option with its conditions: threads run Python bytecode in parallel only while the GIL is actually off, which is why the helper below checks sys._is_gil_enabled() instead of a version number. On both local interpreters it chose ProcessPoolExecutor and printed [285, 328350, 332833500]; on a free-threaded build with the GIL off, the same code picks ThreadPoolExecutor. Close with the trade-offs: pickling cost for processes, single-thread overhead and package support for free threading.

import sys
import sysconfig
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor


def gil_active() -> bool:
    check = getattr(sys, "_is_gil_enabled", None)  # added in 3.13
    return True if check is None else check()


def cpu_work(n: int) -> int:
    return sum(i * i for i in range(n))


def pick_executor():
    # Threads only scale CPU-bound Python code when the GIL is really off.
    return ProcessPoolExecutor if gil_active() else ThreadPoolExecutor


if __name__ == "__main__":
    build = sysconfig.get_config_var("Py_GIL_DISABLED")
    Executor = pick_executor()
    with Executor(max_workers=4) as pool:
        totals = list(pool.map(cpu_work, [10, 100, 1_000]))
    print(f"{sys.version.split()[0]} build={build} gil={gil_active()}")
    print(Executor.__name__, totals)
Choosing a parallelism tool by workload and build
WorkloadDefault buildFree-threaded, GIL off
I/O-boundasyncio or threadsasyncio or threads
CPU-bound pure PythonProcesses or InterpreterPoolExecutorThreads can scale
CPU work in C that releases the GILThreads may already scaleThreads
Unported C extension importedNo changeGIL re-enabled: treat as default

Practice: explain it in sixty seconds

Rehearse three short answers aloud: what the GIL protected (interpreter state and reference counts, not your program's invariants), what changed in 3.13, 3.14 and 3.15, and how you would parallelize a CPU-bound job on each build. Then answer the follow-ups interviewers like: why threads still need locks without the GIL, what happens when an unported extension is imported, and how you would prove a speedup on your own workload.

The Python question bank and the asyncio cancellation article cover the neighboring topics: threading versus asyncio, cancellation semantics and the standard concurrency primitives. Turn each answer into a short recall prompt, for example “what does sys._is_gil_enabled() tell you that Py_GIL_DISABLED does not?”, and review it a few times before the interview. RecallDeck's Python developer track turns answers like these into spaced-repetition cards.

Quick answers

Frequently asked questions

Has Python removed the GIL?

Not by default. The standard CPython build still has a GIL in 3.15. The free-threaded build, which runs without it, has been officially supported since 3.14 but remains a separate, optional build, and whether it ever becomes the default is still undecided.

Does free-threaded Python make my code faster?

Only multi-threaded CPU-bound code that splits across threads, and only when your dependencies support free threading. Single-threaded code runs somewhat slower on the free-threaded build; the official docs cite a few percent, varying by platform and compiler.

How do I install free-threaded Python?

The official macOS and Windows installers have offered free-threaded binaries as an option since 3.13, and the 3.15 notes say the macOS installer now includes free-threading support by default. The executable carries a t suffix, such as python3.14t; source builds use the --disable-gil configure option.

Does free threading change how asyncio works?

A single event loop still runs one callback at a time, and await semantics are unchanged. Python 3.14 notes that asyncio gained first-class support for free-threaded builds, so separate event loops in separate threads can run in parallel, which is a scaling option rather than a new programming model.

Source notes

References and review policy

Information checked on November 7, 2026. Section links identify sources for factual claims and technical explanations. Interpretations, practice scenarios and preparation recommendations are RecallDeck’s editorial work.

From reading to recall

Practice the full interview loop.

RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.

Start studying

Keep going

Languages132 min

Top 100 Go Interview Questions and Answers

Prepare with 100 Go interview questions and detailed answers on slices, interfaces, concurrency, runtime, testing, distributed systems, and reliability.

100 detailed answers