Skip to content

How to use free-threaded Python in a uv project

uv

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 by removing the GIL 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.14t

The 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.14t

This 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 sync

uv 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 pytest

Free-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. To surface these races, run your existing suite in a thread pool with pytest-run-parallel or pytest-freethreaded, which execute each test across many threads at once. pytest-xdist parallelizes across separate processes, so it speeds up the run but cannot expose the in-process data races free-threading introduces.

Roll back if you need to

uv python pin 3.14
uv sync

This restores the standard GIL-enabled interpreter without touching pyproject.toml or the lockfile.

Learn More

Last updated on