How to use free-threaded Python in a uv project
Python 3.14 is the first release where the free-threaded build ships as officially supported, after PEP 703: Making the Global Interpreter Lock Optional in CPython introduced it experimentally in 3.13 and PEP 779: Criteria for supported status for free-threaded Python moved it to phase II in 3.14. This how-to switches an existing uv project to that build, with the dependency-compatibility step that will bite you if you skip it.
If you want to see the performance benefit first on a throwaway project, start with Try Free-Threaded Python with uv.
Install the free-threaded interpreter
uv python install 3.14tThe t suffix requests the free-threaded build. uv keeps standard and free-threaded interpreters side by side, so this does not disturb any existing 3.14 install.
Adjust requires-python first
3.14t is a build variant of Python 3.14, not a separate language version. It satisfies constraints like >=3.14 but not >=3.15. Before pinning, confirm that requires-python in pyproject.toml admits 3.14:
[project]
requires-python = ">=3.14"If requires-python is tighter than the language version of the free-threaded build, pinning fails with an error like The requested Python version '3.14t' is incompatible with the project 'requires-python' value of '>=3.15'. Edit pyproject.toml first, then continue.
Pin the free-threaded build
uv python pin 3.14tThis writes 3.14t into .python-version. Every uv run, uv sync, and uv add in this project now uses the free-threaded interpreter.
Rebuild the virtual environment
uv syncuv detects the new pin, removes the old .venv, and recreates it against 3.14t. Confirm the switch worked:
uv run python -c "import sys; print(sys._is_gil_enabled())"The output should be False. sys._is_gil_enabled() was added in 3.13 so a script can report at runtime which build it is running on.
Check dependencies for free-threaded wheels
Most pure-Python packages work unchanged. C extensions need a free-threaded wheel; otherwise uv falls back to building from source, which needs a compiler and can take minutes per package.
Watch uv sync output for lines that start with Building rather than Downloading. Those are source builds triggered by a missing free-threaded wheel:
$ uv sync
Building some-package==1.2.3
For a broader compatibility survey before you commit to the switch, check the Quansight Labs free-threaded compatibility tracker. Popular scientific-Python packages (NumPy, pandas, SciPy, Pillow) ship free-threaded wheels on recent versions, but niche or unmaintained packages often do not.
When a dependency blocks the switch, options include:
- Wait for upstream. File or follow an upstream issue. Coverage has grown quickly across the 3.13 and 3.14 releases.
- Build from source. Acceptable for small pure-C extensions; painful for packages with heavy build toolchains.
- Isolate the dependency. Move the blocking code into a subprocess or service that runs on a GIL-enabled interpreter while the rest of your project stays free-threaded.
Run your tests
uv run pytestFree-threaded Python exposes thread-safety bugs that the GIL previously hid. Shared mutable state across threads now needs explicit locks, atomic primitives, or message passing. If your project uses threads intentionally, run the test suite under load (for example, with pytest-xdist) and watch for new flakes.
Roll back if you need to
uv python pin 3.14
uv syncThis restores the standard GIL-enabled interpreter without touching pyproject.toml or the lockfile.
Learn More
- Try Free-Threaded Python with uv measures the speedup on a throwaway project
- The official Python free-threading HOWTO covers identification, known limitations, and porting guidance
- PEP 703: Making the Global Interpreter Lock Optional in CPython is the full proposal
- Quansight Labs free-threaded compatibility tracker shows which packages ship free-threaded wheels