How do you check whether CPython is running the free-threaded build?
answer
- Two facts, not one
- Build config versus live process
- A sysconfig variable names the build
- A private sys function reports the live state
- The binary wears a t suffix
basics
~10 sCall sys._is_gil_enabled(): it returns False only while the GIL is actually off. For the build itself, sysconfig.get_config_var('Py_GIL_DISABLED') is 1 on a free-threaded interpreter, and the binary is named python3.14t.
solid answer
~40 sThere are two different questions and two different checks. **Which build am I on** is answered at configure time: `sysconfig.get_config_var("Py_GIL_DISABLED")` is `1` on a free-threaded interpreter and falsy (0 or `None`) on the default one; on POSIX the same build reports `sys.abiflags == "t"` and installs as `python3.14t`. **Is the GIL off right now** is answered by `sys._is_gil_enabled()`, added in 3.13, which returns a bool for the live process. The two can disagree: on a free-threaded interpreter the GIL can still be on, because it was asked for at startup or because an imported C extension that never declared free-threading support caused the interpreter to switch it back on. Free-threading is officially supported as of 3.14 (PEP 779); in 3.13 it shipped as experimental.
code
python · 8 linesimport sys
import sysconfig
free_threaded_build = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
print("free-threaded build:", free_threaded_build)
print("abiflags:", repr(getattr(sys, "abiflags", "n/a")))
print("extension suffix:", sysconfig.get_config_var("EXT_SUFFIX"))
print("GIL enabled right now:", sys._is_gil_enabled())go deeper
Recall the two checks and that they answer different questions: sysconfig.get_config_var("Py_GIL_DISABLED") for the build, sys._is_gil_enabled() for the live process. Know that the free-threaded interpreter is a separate python3.14t binary.
Be ready to explain why a free-threaded build can still report the GIL as enabled, and why the two builds need separately compiled extension modules — the ABI, the cp314t wheel tag and the t extension suffix all follow from that.
Show that you log both values at service startup, after imports, and treat 'free-threaded build with the GIL on' as an alert rather than a curiosity: it costs you the build's overhead and buys nothing.
Own the fleet question: whether both interpreters are installed side by side, how images and lockfiles pin which one runs, and how you would detect a rollout that silently landed on the wrong binary across many services.
## Two facts, not one People say "am I on the no-GIL Python?" as if it were a single yes/no. On CPython 3.14 it is two separate facts, and confusing them is the most common beginner error on this topic. **Fact one: the build.** Free-threading is a compile-time configuration (`--disable-gil`), not a flag you can turn on for a normal interpreter. A free-threaded CPython is a *different binary* built from the same source, and it reports itself: - `sysconfig.get_config_var("Py_GIL_DISABLED")` is `1` on a free-threaded build. On a default build it is falsy — on some platforms `None`, on others `0` — so test it for truthiness, never with `is None`. - On POSIX, `sys.abiflags` is `"t"` on the free-threaded build and `""` on the default one. - The interpreter installs under a `t`-suffixed name — `python3.14t` next to `python3.14`, `python3.14t.exe` on Windows — precisely so both can coexist in one installation. - Compiled extension modules carry the tag too: `sysconfig.get_config_var("EXT_SUFFIX")` reads `.cpython-314-...` on the default build and `.cpython-314t-...` on the free-threaded one, which is why the two builds cannot share an extension binary. **Fact two: the runtime state.** `sys._is_gil_enabled()` returns whether the global lock is engaged *in this process, right now*. On a default build it always returns `True`. On a free-threaded build it normally returns `False` — but not always, and that is the interesting part. ## Why the two can disagree A free-threaded interpreter can end up running with the GIL on for two reasons: 1. **You asked for it.** `-X gil=1` on the command line, or `PYTHON_GIL=1` in the environment, forces the lock on at startup so you can compare behaviour or work around an unsafe dependency without switching binaries. 2. **An extension asked for it.** A C extension module must explicitly declare that it is safe without the lock. If one that has not declared it is imported, the interpreter turns the GIL back on for the whole process and emits a `RuntimeWarning` naming the module. Your process is then running the free-threaded *build* with the lock *enabled* — the worst of both worlds, since you are paying the build's single-threaded overhead and getting none of the parallelism. That is why a health check that only inspects the build configuration is not enough: it tells you what you installed, not what you got. ## What to actually write For a script or a notebook, one line is fine: ```python import sys print(sys._is_gil_enabled()) ``` For anything long-lived, log both values at startup, after all imports have run — the ordering matters, since the GIL can flip during import, not before it. A service that reports "free-threaded build: True, GIL enabled: True" has a dependency problem waiting for you, and you would much rather read that in a startup line than infer it three weeks later from a flat throughput graph. Note the leading underscore in `sys._is_gil_enabled`. It is deliberately private-looking: the CPython authors reserve the right to change it, and it exists mainly for diagnostics and test suites. Use it for exactly that — a startup log line, a test skip condition, a support script — and do not build behavioural branches all over an application on top of it. ## Version boundaries `sys._is_gil_enabled()` arrived with the first experimental free-threaded build in 3.13. The build was *officially supported* in 3.14 under PEP 779, which is when the wheel ecosystem and the packaging tooling started treating it as a first-class target rather than an experiment. On any interpreter older than 3.13 the function does not exist at all, so a compatibility check should reach for it with `getattr` if it has to run on those. On 3.14 itself, both the function and the `sysconfig` variable are always present, whichever build you are on — they simply report different values.
- Can you turn free-threading on for an interpreter you already have installed?No. It is a compile-time configuration, so it needs a separately built interpreter — the `python3.14t` binary from the installer, a distro package, or a source build configured with `--disable-gil`. There is no runtime switch that removes the lock from a default build; the only runtime switch, `-X gil`, works in the other direction on a build that already lacks it.
- Why does sys._is_gil_enabled() have a leading underscore?It is a diagnostic hook rather than a stable public API — CPython reserves the right to change or remove it as free-threading matures. Use it for startup logging, support scripts and test skip conditions. Application logic that needs to behave differently should generally be written to be correct under both builds instead of branching on it.
- Why can't the two builds share the same compiled extension files?Because the C ABI differs — object layout and reference-counting internals change when the lock is removed. That is encoded in the extension suffix (`.cpython-314t-...` versus `.cpython-314-...`) and in the wheel ABI tag `cp314t`, so an installer will not hand a default-build extension to a free-threaded interpreter or vice versa.
Checking the build is like reading the badge on a car's engine; calling sys._is_gil_enabled() is like looking at the dashboard to see whether the handbrake is currently on. Both matter, and they can disagree.
saying these in an interview costs you the question
- Thinking a command-line flag can remove the GIL from a normal build
- Testing the sysconfig variable with `is None` instead of truthiness
- Assuming the t-suffixed binary always has the GIL off
- Believing free-threading is still experimental in 3.14
- Expecting the same compiled extension files to serve both builds