Python 3.13 ships a separate experiment in free-threaded execution
Python 3.13 includes an optional interpreter that can run Python threads across cores. Its extension declarations, synchronization rules, and memory tradeoffs determine what that capability means for applications.
The global interpreter lock has consequences beyond the speed of individual Python instructions. It shapes how libraries coordinate work that mixes Python code and native computation. Python 3.13, released October 7, ships an optional free-threaded build that can execute Python threads across CPU cores. The release is stable, while this build remains an experiment with separate compatibility requirements and significant implementation limits.
That combination needs careful language. Installing ordinary Python 3.13 does not remove its GIL. The release-tagged documentation describes a separate executable, commonly named python3.13t, available through optional Windows and macOS installer components or a source build using --disable-gil. The new experimental JIT is another build choice with a different purpose.
I choose Python because its library ecosystem often supplies capabilities that would otherwise take considerable work to reproduce. That makes the transition's effect on extensions more important to me than an isolated claim about parallel loops. An application needs its required libraries to work under the selected interpreter. Shipping an optional executable gives library authors and users a common implementation against which to establish that support.
Native libraries can still encounter Python coordination limits
PEP 703 describes workloads whose expensive computation happens in native kernels while Python still coordinates the surrounding work. The GIL can restrict that coordination even when a library releases it during a numerical operation. Separate processes provide another route to parallelism, but introduce communication and state-sharing costs. The proposal addresses those constraints on the way a program expresses its work.
This explains why the change is relevant to an ecosystem already rich in C extensions. Native computation does not make every Python operation around it disappear. A pipeline may need to schedule independent work, inspect results, or prepare another operation. If those steps require Python execution, a global lock can still affect how much progress concurrent workers make.
The converse also matters. Allowing parallel Python execution cannot create independent work where the algorithm requires an ordered result. It does not remove contention on a resource shared by the application. The interpreter expands the execution available to threads, while the application determines which dependencies exist between them. That is a change in available execution, not an automatic parallelization of existing code.
Build capability and current execution state differ
The free-threading guide shipped with 3.13.0 gives separate ways to identify build configuration and runtime state. The configuration variable Py_GIL_DISABLED indicates free-threading support when set to one. sys._is_gil_enabled() reports whether the GIL is enabled in the running process. The distinction can be expressed directly:
import sys
import sysconfig
supports_free_threading = (
sysconfig.get_config_var("Py_GIL_DISABLED") == 1
)
gil_enabled = sys._is_gil_enabled()
print(supports_free_threading, gil_enabled)
These queries are useful for different decisions. Build tooling needs to know which interpreter configuration it targets. A runtime investigation needs to know the process's effective execution mode. A binary that supports free threading can still run with its GIL enabled, so the first value alone does not establish the second.
The documented runtime controls include PYTHON_GIL and -X gil. An unsupported native extension can also cause the GIL to be enabled when imported, with a warning. That means imports participate in the effective configuration. Looking only at the command that launched the process can miss a change caused by a dependency loaded afterwards.
Extensions must declare a changed contract
The release notes require C API extensions to be built specifically for the free-threaded interpreter. Multiple-phase modules use the Py_mod_gil slot to state support. Single-phase modules can use PyUnstable_Module_SetGIL. These declarations tell CPython whether the extension supports execution without the global lock. They do not transform an extension's implementation into thread-safe code.
That is a necessary distinction because a declaration is evidence of an author's support decision. It is not a runtime proof that every operation on shared native state is safe. An extension that relied on global serialization needs to revisit that reliance. The support mechanism allows the interpreter to avoid assuming that an unmodified extension has already completed this work.
The release documentation also specifies pip 24.1 or newer for installing packages with C extensions in the free-threaded build. This connects interpreter support to packaging support. A compatible source package, the process that builds its extension, and the resulting installable artifact all have roles in whether an application can use the new executable. Merely changing the interpreter name leaves those requirements in place.
Container locks do not define application transactions
The free-threading guide describes internal locks in built-in containers such as dictionaries and lists. It explicitly treats that behavior as an implementation description rather than a language guarantee about concurrent modification. It recommends application synchronization instead of relying on those internal locks where possible. Protecting an individual container operation does not define the meaning of a sequence of operations performed by an application.
For example, deciding whether work remains and then claiming that work are logically related steps. Even if each underlying container access preserves its own internal consistency, the application still needs an agreement about the combined operation. Removing a global interpreter lock makes such agreements more visible, but their meaning belongs to the program's concurrency design.
This is a place where precise claims are more useful than either blanket confidence or blanket alarm. The guide does not say every built-in operation becomes unsafe. It also does not promise that existing multi-step logic becomes correct through container locking. Code that needs an indivisible state transition must express the synchronization appropriate to that transition.
The experimental implementation has specific costs
The same guide identifies object immortalization as one way the free-threaded build avoids reference-count contention. Certain objects become immortal when another thread first starts after the main thread. These include module-level functions, code objects, modules, and types. Immortal objects are not deallocated, so applications creating many objects in those categories can retain additional memory.
Other limits affect introspection and iteration. Accessing another thread's frame object is unsafe in this build. Sharing one iterator among threads can also produce incorrect results or interpreter failures. These are concrete restrictions on operations that tools or concurrent applications may use. They deserve separate consideration from whether ordinary function calls produce the same results.
The guide reports approximately forty percent single-threaded overhead on the pyperformance suite for this experimental implementation. It attributes much of that cost to disabling the specializing adaptive interpreter in the free-threaded build. Programs dominated by native extension work or I/O can experience a smaller effect. The reported number describes that benchmark suite and implementation, so it cannot stand in for an application's own execution profile.
I regard these documented limitations as part of the feature's present meaning. They explain why an optional build is appropriate even though the containing Python release is stable. The experiment makes parallel execution available while giving extension authors and tool developers concrete constraints to address. Stability of the ordinary release does not imply completion of every experiment included in it.
The JIT and interactive interpreter address different work
Python 3.13 also includes the experimental copy-and-patch JIT described by PEP 744. It compiles optimized traces of internal micro-operations into machine code. LLVM helps generate the templates at build time, without becoming a runtime dependency. This targets execution overhead inside the interpreter, which is separate from allowing multiple threads to execute Python concurrently.
For interactive users, the new default shell provides multiline editing, history browsing, and a paste mode. The release documentation identifies its basis in PyPy's interactive code and provides PYTHON_BASIC_REPL for selecting the older interface. This is useful independent of either runtime experiment. A developer can receive a better interactive editing experience while continuing to run the ordinary GIL-enabled interpreter.
Debuggers also receive more explicit local-variable behavior. In optimized scopes, locals() returns independent snapshots, while frame.f_locals provides a write-through proxy for tools that need to update live variables. The release documentation explains this as a separation between inspecting a snapshot and modifying a frame. It makes those two purposes distinct even when both involve mappings of local names.
The resulting release offers different adoption decisions for different users. Existing applications can target ordinary 3.13 compatibility, while extension authors can prepare explicit free-threading support. Interpreter contributors can exercise the JIT separately. The documentation's configuration and runtime queries make those distinctions observable, including the case where importing an extension changes a free-threaded process back to GIL-enabled execution.