# PyPy: JIT-Compiled Python


PyPy is an alternative Python {{< term "interpreter" >}} that uses a tracing JIT compiler to speed up long-running Python programs. Written in RPython (a restricted, statically typeable subset of Python), it targets compatibility with [CPython](https://pydevtools.com/handbook/explanation/what-is-python.md) while executing pure-Python code roughly 3x faster on average across the PyPy benchmark suite.

{{< callout type="info" >}}
[uv](https://pydevtools.com/handbook/reference/uv.md) installs and manages PyPy alongside CPython. Run `uv python install pypy@3.11` to download a managed PyPy interpreter.
{{< /callout >}}

## When to consider PyPy

PyPy is worth testing when a program spends most of its time in pure Python: tight loops, string processing, protocol parsing, or algorithmic code that does not delegate to C extensions. The JIT needs time to warm up (the first few iterations of a hot loop run in the interpreter before compiled code takes over), so the benefit grows with longer execution times.

Programs that spend most of their time in C extensions (NumPy array operations, database drivers, image processing) see smaller gains or none at all, because the JIT cannot optimize across the boundary into C code.

## Python version compatibility

PyPy tracks CPython releases but lags by one or more minor versions. The current release, PyPy v7.3.23, targets Python 3.11. PyPy also maintains a Python 2.7 interpreter.

[uv](https://pydevtools.com/handbook/reference/uv.md) lists available PyPy versions with:

```bash
uv python list --all-versions | grep pypy
```

## Platform support

PyPy v7.3.23 provides pre-built binaries for:

- Linux x86-64 (manylinux2014 compatible)
- Linux ARM64 (manylinux2014 compatible)
- macOS ARM64 (macOS 11+)
- macOS x86-64 (macOS 10.15+)
- Windows x86-64

## C extension compatibility

PyPy runs C extensions through its cpyext compatibility layer. Extensions compile and load without modification, and popular packages like Django, NumPy, and Scikit-learn pass their test suites. The trade-off is speed: cpyext must emulate CPython's reference-counting memory model, so each call across the Python-to-C boundary pays a marshalling cost. Programs that cross that boundary frequently (calling into C on every iteration of a loop) lose much of the JIT's advantage.

For better performance, PyPy recommends [CFFI](https://cffi.readthedocs.io/) over the C API. CFFI calls are visible to the JIT and avoid the cpyext overhead.

## How the JIT works

PyPy uses a tracing JIT. The interpreter runs code normally until it detects a hot loop. It then records a trace of the operations the loop executes, optimizes that trace (removing redundant type checks, inlining function calls, eliminating dead code), and compiles it to native machine code. Subsequent iterations of the loop run the compiled version.

This approach produces large gains on predictable, loop-heavy code and smaller gains on short-lived processes or code with highly polymorphic call sites where traces cannot stabilize.

## Cons

- **Lags behind CPython.** PyPy targets Python 3.11 while CPython is at 3.14. New language features and standard library additions arrive later.
- **C extension overhead.** The cpyext layer makes C-extension-heavy workloads slower than on CPython. NumPy code that stays in array operations is fine; code that calls into C on every loop iteration is not.
- **Slower startup.** The JIT adds overhead before it warms up. Short-lived scripts and CLI tools often finish before the JIT activates, gaining nothing.
- **Larger binary.** A PyPy installation is larger than a CPython installation due to the JIT compiler and its associated runtime.
- **Smaller ecosystem coverage.** Most pure-Python packages work, but some C extensions that rely on CPython implementation details (internal struct layouts, unstable API surfaces) may fail or behave differently.
