Python's JIT Compiler: From the 3.13 Experiment to Realistic Expectations
Starting with Python 3.13, CPython ships an experimental JIT compiler. If you’ve seen headlines along the lines of “Python finally gets fast with a JIT,” it’s worth recalibrating your expectations. The JIT is real — but it won’t make your code noticeably faster today. This post covers how the JIT works, how to turn it on, and where things stand as of mid-2026.
The momentum behind Faster CPython #
“Python is slow” is an old refrain, but CPython has been getting steadily faster in recent years. At the center of that change was the Faster CPython project. Led by Python’s creator Guido van Rossum and Mark Shannon, the team’s first major achievement was the specializing adaptive interpreter (PEP 659) in 3.11. When the same bytecode location keeps seeing the same types, that location gets swapped out for a type-specialized instruction. That alone made 3.11 about 1.25x faster than 3.10 on average.
3.12 refined the internals, and 3.13 is where the JIT finally arrived. If specialization was “the optimization you can do inside the interpreter,” the JIT is the next step: “the optimization that generates machine code directly.”
Interpreter vs. JIT #
An interpreter reads bytecode one instruction at a time, determines what each instruction means, and then runs the corresponding C code. That dispatch cost accumulates on every single instruction.
A JIT (Just-In-Time) compiler picks out the regions that execute frequently at runtime and compiles just those regions to machine code. Think of a chef: a brand-new dish means checking the recipe line by line, but one that goes out hundreds of times a day becomes muscle memory — no recipe needed. The hotter the loop, the bigger the payoff from eliminating that lookup cost.
Java’s HotSpot and JavaScript’s V8 are the classic examples of making dynamic languages fast this way. The Python world has long had PyPy, a JIT-based implementation, but 3.13 is the first time a JIT has landed in CPython itself.
copy-and-patch: a JIT that pastes templates #
The 3.13 JIT (PEP 744) uses a technique called copy-and-patch. It’s a relatively new approach, introduced in 2021 research by Haoran Xu and Fredrik Kjolstad.
A traditional JIT carries an entire compiler infrastructure around at runtime. It builds an intermediate representation, runs optimization passes, allocates registers, and emits machine code. Powerful, but a heavy burden to implement and maintain.
copy-and-patch takes a different approach.
- At build time: when CPython itself is built, LLVM pre-generates a machine-code fragment — a stencil — for each micro-operation.
- At run time: when a hot code path is detected, the corresponding stencils are copied into memory, and the blank holes are patched with actual addresses and constants, then stitched together.
No compiler is needed at runtime, and machine-code generation is essentially “copy + fill in the blanks,” so it’s extremely fast. The codebase is small enough that a small team can maintain it. For CPython — which must support every platform and ship on a fixed release cadence — that simplicity was the decisive advantage. It also fit neatly on top of the Tier 2 micro-operation traces introduced in 3.13.
Enabling it in 3.13 #
The official 3.13 release binaries do not include the JIT. You have to build from source.
./configure --enable-experimental-jit
makeThere’s also an option to include the JIT in the build but leave it off by default. In that case, you enable it at runtime with an environment variable.
./configure --enable-experimental-jit=yes-off
make
# enable only when running
PYTHON_JIT=1 python myapp.pyOn Windows, use PCbuild/build.bat --experimental-jit. Either way, trying the JIT in 3.13 meant setting up your own build environment, so in practice it was a feature for core developers and early adopters.
Where things stand in 3.14 #
With 3.14, released in October 2025, the barrier dropped. The official macOS and Windows installers now include an experimental JIT build. It’s still off by default and is enabled via an environment variable.
PYTHON_JIT=1 python myapp.pyYou can confirm whether it’s on through the sys._jit namespace added in 3.14.
import sys
print(sys._jit.is_available()) # whether this build includes the JIT
print(sys._jit.is_enabled()) # whether it's currently enabledInternally, hot-path detection and memory usage were refined as well. One distinction worth making, though: 3.14 is indeed faster than 3.13 overall, but most of the credit goes not to the JIT but to improvements in the interpreter itself (such as the tail-call-based interpreter). The JIT is not yet “a proven accelerator worth enabling by default” — it’s “a feature that now ships in official binaries so anyone can experiment with it.”
Reality check: how much faster is it right now? #
So how much do you gain by turning on the JIT today? The honest answer: almost nothing.
- The official documentation itself says “some programs may be faster,” and explicitly notes that the performance gains are not yet significant.
- In published benchmarks, the JIT’s effect averages in the low single-digit percentages, and some workloads actually get slower. Miguel Grinberg’s measurements right after the 3.14 release likewise found no meaningful difference between the JIT build and the regular build.
- Short-lived scripts and CLI tools are at a disadvantage under the JIT because of warm-up costs. A hot loop has to run long enough for the compiled code to pay for itself.
This isn’t a failure — it’s the stage everyone expected. copy-and-patch’s first goal was never “a fast JIT right now” but “getting a machine-code generation foundation safely into the core.” Building optimizations on top of that foundation is what’s happening now. In short: at this point the JIT matters to developers interested in CPython internals and to users who want to run their own benchmarks. It’s not yet at the stage where production services can cut costs with it.
How it relates to existing acceleration options #
“Make Python faster” is not a new demand, and proven options already exist.
- PyPy: a separate Python implementation with a JIT. For pure Python code it remains incomparably faster than the CPython JIT, but C extension compatibility is its weak spot.
- Cython and mypyc: compile your hotspots to C.
- Rust extensions (PyO3): write the performance-critical parts in Rust.
- NumPy vectorization: for numerical work, still the highest-impact choice.
For now, the practical answer hasn’t changed: find the slow spots through measurement and apply one of the tools above. How to find those slow spots is covered in Modern Python Advanced #7. The significance of the CPython JIT is that it’s preparing, inside the core itself, a future where “just using the latest Python makes you faster” — no workarounds required.
How it relates to free-threading #
“Faster Python” has two axes: the JIT, which raises single-threaded execution speed, and free-threading, which lets you use multiple cores at once. They are separate projects moving at different speeds. Free-threading reached the officially supported stage in 3.14 (PEP 779), while the JIT remains experimental. Also, as of 3.14, the JIT cannot be used in free-threaded builds. Combining the two axes in a single build is a task for the future.
What to watch next #
There’s one wildcard. In May 2025, Microsoft laid off most of the Faster CPython team in a round of large-scale layoffs. The project’s biggest sponsor and driving force dropped out. That said, JIT development continues, led by core developers and the community, and the 3.14 improvements came out of that same effort.
Three things to watch going forward:
- The performance curve from 3.15 onward: the key question is whether the optimizations stacking on the foundation break past the low-single-digit-percent wall.
- When it’s enabled by default: the real turning point comes when a version ships with the JIT on by default in official builds.
- Combining with free-threading: both axes have to work in a single build for “fast and parallel Python” to be complete.
Wrap-up #
CPython 3.13 introduced an experimental copy-and-patch JIT, and starting with 3.14 you can try it from the official macOS and Windows installers with nothing more than PYTHON_JIT=1. Its simple design — copying machine-code templates generated at build time and patching them together — is what made it manageable for CPython, but the current performance effect averages only a few percent. The accurate way to understand it today is not as “a switch that makes things fast” but as “a foundation that will make things fast later.” Solve today’s performance problems with profiling and the proven acceleration tools, and watch the JIT against two markers: the performance curve from 3.15 onward and the version that enables it by default.