Engineering • 12 min
Python's Free-Threading Mode: Is It Time to Care?
Python's Free-Threading Mode: Is It Time to Care?
Python 3.13 shipped with something people argued about for two decades: the ability to disable the GIL.
Python 3.14 made it officially supported.
But "officially supported" doesn't mean "deploy it on Friday." And it definitely doesn't mean "it's fast."
Where things actually stand
Python 3.13 (October 2024): Free-threading shipped as experimental. You compile a separate binary (python3.13t). The specializing adaptive interpreter was disabled in free-threaded mode, causing a 20-40% single-threaded performance regression. That's not a minor hit.
Python 3.14 (October 2025): Free-threading is officially supported (Phase II, per PEP 779). The specializing interpreter is re-enabled. Single-threaded penalty dropped to 5-10%. First version where free-threading is a realistic option.
Python 3.15 (alpha): Current development. Free-threaded builds show ~9% overhead on Linux x86_64, ~6% on macOS ARM64 vs. GIL-enabled on same version.
Phase III — making free-threaded the default — has no PEP. No timeline. Realistic estimate: 2028-2029.
The GIL is removable. The GIL is not removed. Those are different things.
The real benchmark numbers
Not opinions. Measurements. From Meta's free-threading-benchmarking repo (pyperformance suite, April 2026).
Single-threaded: you pay a tax
| Build | vs Python 3.12.6 | vs GIL-enabled same version |
|---|---|---|
| 3.15.0a8 (GIL) | 1.063x faster | baseline |
| 3.15.0a8 (NOGIL) | 1.027x slower | ~9% slower (Linux), ~6% (macOS ARM64) |
Every Python object is larger now. The PyObject struct went from 16 bytes to ~32 bytes — it now includes ob_tid, ob_mutex, ob_gc_bits, ob_ref_local, ob_ref_shared. That's the cost of safe concurrent access.
The garbage collector changed from generational (3 generations) to non-generational (single generation with stop-the-world pauses). pymalloc was replaced with mimalloc.
6-9% slower, 15-20% more memory. That's the price of free-threading.
Multi-threaded: where it wins
- Mandelbrot computation: Near-linear scaling with threads. GIL-enabled: zero scaling. Adding threads does nothing.
- Monte Carlo simulation: ~N× speedup with N threads.
- PyTorch inference: The GIL becomes the bottleneck with as few as 8-10 threads. Free-threading eliminates this.
- Dose-3D project: "Astonishing" results — less than half a day to adapt, multiprocessing bottleneck gone.
The pattern: CPU-bound Python code currently using multiprocessing is the primary beneficiary. If you're on asyncio or I/O-bound, free-threading doesn't help you.
The ecosystem support picture
This is the real gating factor. Not Python itself — the packages.
What works
| Package | First version with support |
|---|---|
| NumPy | 2.1.0 |
| SciPy | 1.15.0 |
| pandas | 2.2.3 |
| PyTorch | 2.6.0 |
| scikit-learn | 1.6.0 |
| Cython | 3.1.0 (critical — many packages depend on it) |
| pydantic | 2.11.0 |
| Pillow | 11.0.0 |
The scientific Python stack is solid. If you're in this space, free-threading is usable today.
What doesn't work yet
- OpenCV — no release
- grpcio — no release
- vLLM — in progress
- JupyterLab — doesn't fully work (use free-threaded kernel with GIL-enabled Jupyter)
- spaCy — discussion open
The silent killer: importing a C extension without Py_mod_gil support silently enables the GIL. Your free-threaded Python becomes GIL-enabled the moment you import an unsupported package. You might not even notice.
How to try it
You don't need to compile from source:
# macOS
brew install python-freethreading
# Linux (Ubuntu)
sudo apt-get install python3.14-nogil
# Using uv
uv venv --python 3.14t
# conda-forge
conda create -n nogil -c conda-forge python-freethreading
Verify:
import sys
sys._is_gil_enabled() # Should be False
Windows gotcha: The Python.org installer shares site-packages between GIL and free-threaded builds. Use separate virtual environments.
The bugs you'll hit
Borrowed reference unsafety. PyDict_GetItem returns borrowed references that can become invalid. New PyDict_FetchItem APIs exist but aren't adopted everywhere. Silent data corruption, not crashes.
Stop-the-world GC pauses. Single-generation GC with stop-the-world pauses. Python 3.14 adds incremental GC to mitigate, but latency-sensitive apps will feel it.
Data races in pure Python. The interpreter won't crash. But shared mutable state (dict caches, module-level config) can produce wrong results. The GIL masked these bugs. Removing it reveals them.
Extension compatibility. The biggest one. Import a non-free-threaded C extension and the GIL silently re-enables. You think you're running free-threaded. You're not.
PyPy vs free-threaded CPython
PyPy has never had a GIL in the traditional sense. Its JIT is generally 4-5x faster than CPython for single-threaded workloads.
So why isn't everyone on PyPy?
C API compatibility. PyPy's compatibility layer has significant overhead. NumPy and SciPy run slower on PyPy than CPython. The entire scientific Python ecosystem assumes CPython's ABI.
Free-threaded CPython keeps full C API compatibility. That's the point — you get threading without leaving the CPython ecosystem.
| CPython (GIL) | CPython (free-threaded) | PyPy | |
|---|---|---|---|
| Single-thread perf | Baseline | -5 to -10% | +400% (JIT) |
| Multi-thread perf | Blocked | Near-linear scaling | Limited by cpyext |
| C API compat | Full | Full | Partial |
| Package compat | Full | Growing | Poor (C exts) |
PyPy is faster. Free-threaded CPython is more compatible. For most teams, compatibility wins.
Should you migrate?
No, if:
- Your code is I/O-bound (web APIs, database queries)
- You're on asyncio and it's working
- You depend on packages without free-threaded wheels
Maybe, if:
- CPU-bound workloads currently using multiprocessing
- Memory overhead of multiprocessing is a bottleneck
- Your dependency tree has free-threaded wheels
Yes, if:
- Scientific computing with C-extension-heavy workloads
- Building new infrastructure for the next 5+ years
- You've verified your full dependency tree works
What I'm doing
I'm not migrating production systems. But I'm running our test suites against a free-threaded Python binary. Because in 12-18 months, when the ecosystem catches up, I want to know exactly what breaks.
Start testing against it now. Don't deploy it yet. That's the right posture for 2026.
The GIL's removal is the most significant change to Python's concurrency model in the language's history. But significant doesn't mean ready. It means important enough to start paying attention.
This is an independent comparison with no affiliate links. I'm not sponsored by any tool or language mentioned. Benchmark data from Meta's free-threading-benchmarking repository and the pyperformance suite.
Work with us
Let's build something together
We build fast, modern websites and applications using Next.js, React, WordPress, Rust, and more. If you have a project in mind or just want to talk through an idea, we'd love to hear from you.