CPython vs PyPy and Alternative Implementations

The Python language is a specification; CPython — the interpreter written in C that you download from python.org — is only its reference implementation, the one against which every other implementation measures itself and the one whose behaviour is, in practice, treated as ground truth even where the spec is silent (see Python Language vs CPython Implementation). But CPython is not the only Python. PyPy trades CPython’s simple reference-counting interpreter for a meta-tracing just-in-time (JIT) compiler that often runs pure-Python code several times faster. GraalPy, Jython, and IronPython re-host Python on other virtual machines (the GraalVM, the Java Virtual Machine, and the .NET Common Language Runtime) to get interoperability with those ecosystems. MicroPython and its fork CircuitPython shrink the language to fit microcontrollers with kilobytes of RAM. And a graveyard of performance forks — Unladen Swallow, Pyston, Stackless, and most of Cinder — either died or were folded back into CPython itself. This note is the vault’s single catalogue of those alternatives; alt-implementation claims live here rather than scattered across the Python Internals MOC.

Mental Model: One Spec, Many Engines

The crucial distinction is that “Python” names a language, defined by the language reference and the standard-library documentation, while an implementation is a concrete program that accepts that language and runs it. CPython is the implementation that the core developers maintain and that the specification is, circularly, written to describe — when the reference manual says behaviour is “implementation-defined,” it usually means “whatever CPython happens to do.” Every alternative therefore faces the same tension: how closely to mimic CPython’s observable behaviour (including its bytecode, its C API, its reference-counting object lifetimes, and its quirks) versus how much to deviate in pursuit of speed, a smaller footprint, or host-VM integration.

graph TD
    SPEC["Python language spec<br/>(reference manual + stdlib docs)"]
    SPEC --> CP["CPython<br/>reference impl, C, refcount + cyclic GC"]
    SPEC --> PYPY["PyPy<br/>RPython, meta-tracing JIT, real GC"]
    SPEC --> GRAAL["GraalPy<br/>Truffle/GraalVM, partial-eval JIT"]
    SPEC --> JY["Jython<br/>JVM, Python 2 only"]
    SPEC --> IRON["IronPython<br/>.NET CLR/DLR"]
    SPEC --> UP["MicroPython / CircuitPython<br/>microcontrollers, Py3.4 subset"]
    CP -. "forks, mostly folded back" .-> FORK["Cinder, Pyston, Unladen Swallow, Stackless"]
    style CP fill:#cde
    style SPEC fill:#efe

Diagram: the language specification sits at the top; CPython is the reference implementation everyone else is compatibility-tested against. Independent implementations (PyPy, GraalPy, Jython, IronPython, MicroPython) re-implement the language on different runtimes and make different trade-offs; performance-oriented CPython forks (bottom) mostly either died or were upstreamed back into CPython. The insight: “alternative Python” splits into two very different categories — clean-room re-implementations on other VMs, and patches/forks of CPython itself.

CPython: The Reference Everyone Measures Against

CPython is the baseline, so its design choices define what “compatible” means. It compiles source to bytecode and runs it on the specializing adaptive evaluation loop; it manages memory primarily by reference counting backed by a cyclic garbage collector; and it exposes a C API that the entire native-extension ecosystem (NumPy, lxml, database drivers, machine-learning frameworks) is compiled against. Two of those three choices — refcounting and the C API — are precisely what make CPython simultaneously easy to extend in C and hard to make fast or parallel, which is why the alternatives exist. When this note says an implementation is “X% compatible,” the implicit yardstick is “runs code and C extensions written for CPython.” For the language-vs-implementation framing in depth, see Python Language vs CPython Implementation; this note assumes it and focuses on the alternatives.

PyPy: Meta-Tracing JIT on the RPython Toolchain

PyPy is the most important alternative implementation, because it is the one that is both a near-drop-in replacement for CPython and dramatically faster on the right workloads. Its homepage describes it as “a fast, compliant alternative implementation of Python” and claims that “on average, PyPy is about 3 times faster than CPython 3.11” (pypy.org, built 2026-05-27). As of that date PyPy supports the Python 3.11 and 2.7 language versions — note that it characteristically trails CPython’s latest by a few releases, which is the single biggest practical reason teams hesitate to adopt it.

The RPython toolchain

What makes PyPy unusual is how it is built. PyPy’s interpreter is not hand-written in C; it is written in RPython (“Restricted Python”), a statically-analysable subset of Python, and then mechanically translated to C by the RPython translation toolchain. The toolchain is “for generating interpreters for dynamic programming languages” — you “take source code written in RPython, apply the RPython translation toolchain, and end up with PyPy as a binary executable” (PyPy introduction). Because RPython “uses the same syntax as Python,” the interpreter is far easier to experiment with than a C interpreter, while still translating down to a fast native binary. The translation pipeline lives in the rpython directory and runs through flowspace, annotator, and rtyper stages that turn the dynamic RPython source into typed flow graphs and then C (PyPy architecture).

Meta-tracing: tracing the interpreter, not your program

The JIT is the headline feature, and its design is subtle. A conventional tracing JIT records the hot path of the user’s program and compiles it. PyPy instead uses a meta-tracing JIT that “traces the interpreter written in RPython, rather than the user program that it interprets” (PyPy architecture). In other words, when a Python loop gets hot, PyPy traces the sequence of interpreter operations executed while running that loop, optimises that trace (the optimizer lives in rpython/jit/metainterp/optimizer), and emits machine code through a per-architecture backend (rpython/jit/backend/<machine-name>). The payoff is generality: because the JIT traces the RPython interpreter rather than Python specifically, the same machinery works for any language implemented in RPython, and the Python-specific interpreter author gets a JIT essentially for free.

A real garbage collector, not reference counting

PyPy makes a clean break from CPython’s memory model: it “does not use reference counting internally but always a garbage collector,” and there are “no Py_INCREF/Py_DECREF equivalents in RPython code” (PyPy architecture). The production collector is implemented in RPython itself, in rpython/memory/gc/incminimark.py (the name signals an incremental minimark generational design, though the architecture page fetched here states only the file location and that it is “a real GC” — see flag). This is liberating for performance — no per-operation refcount traffic — but it has an observable consequence: objects are not collected the instant their last reference disappears. Code that relies on CPython’s deterministic destruction (__del__ firing immediately, files closing when a variable goes out of scope) can break under PyPy, which is one of the most common porting surprises.

The speed/compatibility trade-off: cpyext

PyPy’s Achilles’ heel is the C-extension ecosystem. Pure-Python code is where PyPy shines; C extensions are where it struggles, because they are written against CPython’s C API, which bakes in CPython internals (the PyObject* memory layout, reference counting, borrowed references) that PyPy does not use. PyPy provides cpyext, a compatibility layer that lets CPython C extensions be compiled and run inside PyPy, but it is slow by construction. As the PyPy team’s own deep-dive explains, “since the low-level layout of PyPy W_Root objects is completely different than the one used by CPython, we cannot simply pass RPython objects to C; we need a way to handle the difference,” so “we need to pay some penalty for all the conversions between W_Root and PyObject*” (Inside cpyext, 2018). The post enumerates the costs: PyPy objects “can potentially move and change their underlying memory address” yet must be represented as “fixed-address PyObject*”; the runtime keeps “paying the border-crossing cost for trivial operations which are called very often, such as Py_INCREF”; and functions returning borrowed references are a special headache because PyPy “cannot simply create a PyObject* on the fly, because the caller will never decref it and it will result in a memory leak.” The result is that a C extension that is fast under CPython can be slower under PyPy — the exact opposite of the speedup PyPy promises for pure Python. This is why the PyPy project pushes cffi (which PyPy supports natively and efficiently) over the C API for new code.

Verified (2026-06-01)

PyPy’s current top language version is Python 3.11, shipped in PyPy v7.3.23 (built 2026-05-27), per the PyPy download page. This is point-in-time: PyPy adds new Python-version support periodically, so re-check the download page if a newer line (e.g. 3.12) matters.

GraalPy: Python on the GraalVM via Truffle

GraalPy (project name graalpython, maintained by Oracle) is, per its own GitHub README, “a high-performance implementation of the Python language for the JVM built on GraalVM,” and per its homepage “a high-performance embeddable Python 3 runtime” that can “speed up Python applications with the Graal JIT” (oracle/graalpython; graalpy.org). The README states plainly that “GraalPy is a Python 3.12 compliant runtime” (oracle/graalpython), making it the most current-tracking of the non-CPython implementations as of 2026, with releases versioned on the GraalVM calendar scheme — the latest is GraalPy 25.0.3, released 2026-05-21 (oracle/graalpython). Architecturally, GraalPy is implemented as a self-optimising abstract-syntax-tree (AST) interpreter on GraalVM’s Truffle language framework, which the Graal compiler then partial-evaluates into JIT-compiled machine code — a different route to the same goal as PyPy’s meta-tracing.

The Truffle framing is primary-confirmed: GraalPy’s own implementation-details document is written in Truffle terms throughout — it describes materializing “the locals in the Truffle frame” onto the heap and using “the TruffleSafepoint mechanism” for GIL acquisition (GraalPy implementation details). Partial-evaluation by the Graal compiler is the standard Truffle compilation model that every Truffle-hosted language inherits.

Not covered: the exact C-extension execution mechanism (as of 2026-06-01)

This note deliberately does not assert how GraalPy executes native/C extensions. GraalPy’s own native-extensions doc (fetched this session) describes that support only as “experimental.” It is widely held that GraalPy runs C extensions by compiling them to LLVM bitcode executed on the GraalVM LLVM Runtime (historically “Sulong”), but that specific mechanism was not pinnable to a directly-quoted GraalPy primary here, so it is omitted rather than stated. To confirm if needed: read the GraalPy Native-Extensions guide and the GraalVM LLVM Runtime docs.

GraalPy’s distinctive selling points, per its README and homepage, are Java interoperability — “use Python in Java applications on GraalVM JDK, Oracle JDK, or OpenJDK,” and conversely use Java libraries from Python — and the ability to bundle a Python app plus its dependencies into a single standalone native executable (oracle/graalpython; graalpy.org). On C extensions the README is explicit that “support for native extension modules is considered experimental, but you can already install packages like NumPy, PyTorch, or Tensorflow,” and quantifies compatibility: “for 97% of those packages a recent version can be installed on GraalPy and GraalPy passes over 60% of all tests of all packages combined” (oracle/graalpython). The practical niche: teams already on the JVM who want real Python with Java interop, or who want Python embedded in a larger polyglot application.

Jython: Python on the Java Virtual Machine

Jython compiles Python to JVM bytecode and gives Python “the benefits of running on the JVM and access to classes written in Java” (jython.org). Its bidirectional Java integration — Python code importing Java classes directly, Java code embedding a Python interpreter — made it popular for scripting Java applications and for tools like the Hadoop ecosystem and Apache JMeter historically.

The blunt status: the download page states “The current version of Jython is 2.7.4” — a Python 2 implementation. A Python 3 port (“Jython 3”) exists only as in-development roadmap and MVP snapshots in the project’s GitHub repository, so as of 2026 there is no released Python-3-compatible Jython. Because Python 2 reached end-of-life on 1 January 2020, this effectively makes Jython a legacy choice for new work — its single most important fact is that it is stuck on a dead language version.

Verified (2026-06-01)

No Python-3 Jython has shipped. The jython.org download page states directly that “The current version of Jython is 2.7.4” — a Python 2 implementation; “Jython 3” exists only as in-development roadmap/MVP snapshots, not a released build. Point-in-time — a Python-3 Jython could still land, so re-check the download page if it matters.

IronPython: Python on the .NET CLR

IronPython is “an open-source implementation of the Python programming language which is tightly integrated with .NET” (ironpython.net). It runs on the Common Language Runtime (CLR) and the Dynamic Language Runtime (DLR), and allows two-way library use: “IronPython can use .NET and Python libraries, and other .NET languages can use Python code just as easily.” It is supported by the .NET Foundation under the Apache 2.0 licence.

Unlike Jython, IronPython has shipped a Python-3 line. As of the data fetched:

  • IronPython 2 implements Python 2.7 (latest 2.7.12, released 2022-01-21).
  • IronPython 3 implements Python 3.4 (latest 3.4.2, released 2024-12-19).

The key caveat is the language level: IronPython 3 targets the Python 3.4 grammar and semantics, so a great deal of modern Python (f-strings from 3.6, the walrus operator from 3.8, structural pattern matching from 3.10, and so on) is unavailable. It is a viable choice for embedding Python scripting inside a .NET application, but not for running a modern Python codebase unchanged.

Verified (2026-06-01)

IronPython 3’s latest release remains v3.4.2 (published 2024-12-20), per the IronLanguages/ironpython3 releases — no newer release has shipped and the targeted language level is still Python 3.4. Point-in-time; re-check releases if currency matters.

MicroPython and CircuitPython: Python for Microcontrollers

MicroPython is “a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers and in constrained environments” (micropython.org). Created by Damien George, it is engineered to fit within roughly “256k of code space and 16k of RAM” — orders of magnitude smaller than CPython — while still offering an interactive REPL, arbitrary-precision integers, closures, list comprehensions, generators, and exception handling, plus hardware-access modules like machine. The current release line is v1.28.x (v1.28.0, published 2026-04-06 per the micropython releases), adding support for newer microcontrollers (RP2350, ESP32-C6) and improved RISC-V native-code generation.

Verified (2026-06-01)

Confirmed against primary release pages: MicroPython’s latest is v1.28.0 (published 2026-04-06, micropython/micropython releases); CircuitPython’s latest stable is 10.2.1 (published 2026-05-12, adafruit/circuitpython releases) — the 10.x line, not the older 9.2.x. Both are point-in-time; re-check the release pages for currency.

The compatibility story is defined by which Python it implements: per the official differences page, “MicroPython implements Python 3.4 and some select features of Python 3.5 and above” (MicroPython differences from CPython) — so it tracks the Python 3.4 grammar (exceptions, with, yield from) plus a hand-picked set of later additions (async/await, assignment expressions, some newer syntax), but deliberately not the whole modern language. It also omits most of the standard library, providing instead a small reimplemented subset (the u-prefixed modules, e.g. utime, ujson) plus embedded-specific modules. The trade-off is explicit: it sacrifices completeness and library breadth for a footprint that runs on a $4 board.

CircuitPython is Adafruit’s fork of MicroPython, “designed to simplify experimenting and learning to code on low-cost microcontroller boards” (circuitpython.org). Adafruit credits Damien George and the MicroPython community directly and contributes back to MicroPython financially and in code. The differences are oriented toward education and ease of use: CircuitPython presents the board as a USB drive so you edit code.py and it runs on save (no separate flashing step), maintains a large library of device drivers, and prioritises a beginner-friendly workflow over MicroPython’s broader feature set and performance options. Its stable line is the 10.x series (10.2.1, published 2026-05-12 per the circuitpython releases), supporting over 600 boards.

Cinder and CinderX: Meta’s Fork, Mostly Folded Back

Cinder is “Meta’s fork of the CPython runtime” — originally an internal, performance-oriented fork developed for the Instagram Django web service (facebookincubator/cinder). Meta open-sourced it not as a product but to enable conversation about upstreaming the work into CPython. As the repository itself warns, “the name ‘cinder’ here is historical”: for Python 3.10 onward Meta restructured the project from a fork into a Python extension called CinderX (the “X” denoting extension), to track stock CPython more easily. Per the CinderX README, “Python 3.14 is the first version of stock CPython that CinderX supports” (cinderx README).

CinderX bundles a JIT compiler (“just-in-time compilation of Python bytecode to native machine code”), Static Python (“a stricter form/subset of Python, for type safety and optimization”), plus a parallel garbage collector and lightweight interpreter frames. It is “used in production at Meta for use-cases like the Instagram Django service” but is explicitly “experimental for external users,” with weekly PyPI releases — i.e. not a supported general-purpose distribution.

The most important point for this catalogue is that much of Cinder’s value has flowed back into mainline CPython. The clearest example is immortal objects: PEP 683 (“Immortal Objects, Using a Fixed Refcount,” authored by Eric Snow and Eddie Elizondo, “Python-Version: 3.12”) landed in CPython 3.12 (PEP 683). Its motivation is exactly the forking-web-server workload Cinder was built for: “for some applications it makes sense to get the application into a desired initial state and then fork the process for each worker,” which “can result in a large performance improvement, especially memory usage,” and the PEP notes that “several enterprise Python users (e.g. Instagram, YouTube) have taken advantage of this” — but that ordinary “refcount semantics drastically reduce the benefits and have led to some sub-optimal workarounds,” because every refcount write to a shared object dirties a copy-on-write memory page. Marking long-lived objects immortal (a fixed, never-decremented refcount) avoids that page churn. More broadly, the ideas in Cinder — adaptive specialisation, immortalisation, frame optimisation — overlap heavily with the Faster CPython effort, so the trend is convergence: Cinder is less an “alternative Python” than a research fork feeding the reference implementation. See Immortal Objects for the mechanism.

The Graveyard: Unladen Swallow, Pyston, Stackless

A recurring pattern in Python’s history is the ambitious performance fork that fails to displace CPython — usually because keeping a fork in sync with CPython, while remaining compatible with the C-extension ecosystem, is harder than the speedup is worth.

Unladen Swallow (≈2009–2011) was Google’s attempt to add an LLVM-based JIT to CPython, proposed for merge in PEP 3146 (“Merging Unladen Swallow into CPython,” by Collin Winter, Jeffrey Yasskin, and Reid Kleckner). The PEP aimed to merge the JIT into a CPython development branch “while maintaining source compatibility with CPython 2.6.4 applications and C extension modules,” but its final status is Withdrawn (PEP 3146). The retrospective reasons are now grounded in both the PEP’s own withdrawal section and the LWN retrospective. PEP 3146 confirms the technical failures plainly: the team “had to turn our attention away from performance to fix a number of critical bugs in LLVM’s JIT infrastructure”; the original “goal for Unladen Swallow was a 5x performance improvement over CPython 2.6. We did not hit that, nor to put it bluntly, even come close”; and the build regressed memory (up to 7.92×) and startup (2.5× slower), which the PEP itself calls a “blocking issue for final merger into the py3k branch” (PEP 3146). The organisational reasons come from the LWN retrospective quoting Reid Kleckner: “the signals we were getting from python-dev were not good. There was an assumption that if Unladen Swallow were landed in py3k, Google would be there to maintain it, which was no longer the case”; “only a few developers seemed excited about the new JIT”; and absent sustained backing the merged code “would have been disabled by default and ripped out a year later after bitrot” (LWN 2011). The lesson — that a general-purpose compiler back-end (LLVM) is not a substitute for Python-specific, profile-guided specialisation — directly informs why CPython’s eventual JIT (The CPython JIT Compiler) is a copy-and-patch design fed by an adaptive interpreter rather than an LLVM bolt-on.

Pyston, per its own repository, is “a performance-optimizing JIT for Python, and is drop-in compatible with the standard Python interpreter” (pyston/pyston). “Pyston full” was a fork of CPython 3.8.12 with a JIT and other optimisations that “is roughly 30% faster than CPython on web serving macrobenchmarks.” Recognising that adopting a whole forked interpreter is a hard sell, the team also shipped pyston-lite, an extension module available for Python 3.7–3.10 that “is roughly 10% faster on macrobenchmarks” — the same fork-vs-extension realisation Meta later reached with CinderX. The project is de facto dormant rather than formally archived: as of 2026-06-01 the GitHub repository is not marked archived but its last commit dates to 2024-08-12 (per gh api repos/pyston/pyston), i.e. roughly two years stale. (The project was independently developed and at one point funded through Anaconda before being wound down; that corporate-lineage detail is from secondary reporting, not the repository.)

Stackless Python is a CPython fork that “avoids depending on the C call stack for its own stack,” giving lightweight tasklets (microthreads), channels, and serializable tasks for cooperative, single-core scheduling — but it does not remove the GIL (Wikipedia: Stackless Python). Its GitHub repository is confirmed archived with a final push of 2025-02-13 (per gh api repos/stackless-dev/stackless, retrieved 2026-06-01) — i.e. archived in February 2025 and discontinued. Its lasting legacy is indirect: its concepts live on in PyPy and, most importantly, in the greenlet extension for ordinary CPython, which underpins gevent and similar cooperative-concurrency libraries.

Verified (2026-06-01)

Status confirmed via the GitHub API: Stackless is archived (archived: true, last push 2025-02-13). Pyston is not formally archived (archived: false) but is dormant — last commit 2024-08-12 — and its current README no longer carries a “no longer maintained” banner (the prose above was corrected accordingly). Both are point-in-time.

When to Choose Which

The honest decision guide, given the trade-offs above:

  • Default to CPython. It is the reference, tracks the latest language, and has unmatched C-extension support. Choose it unless you have a specific reason not to.
  • PyPy when your workload is long-running, CPU-bound pure-Python (web back-ends, simulations, parsers) and does not lean heavily on C extensions — and you can tolerate trailing the latest Python version and non-deterministic destruction.
  • GraalPy when you live on the JVM and want Python interop with Java, or want to ship a polyglot/standalone executable; it tracks Python 3.12 and is the most current non-CPython option.
  • Jython essentially only for maintaining legacy Python-2 code embedded in Java; avoid for new work (no released Python 3).
  • IronPython for embedding Python scripting in a .NET application, accepting the Python-3.4 language ceiling.
  • MicroPython / CircuitPython when the target is a microcontroller — there is no realistic alternative; pick CircuitPython for education/ease, MicroPython for breadth and performance.
  • Cinder/CinderX, Pyston, Stackless are generally not something you adopt today: Cinder/CinderX is a Meta-internal research fork (its wins are flowing into CPython anyway), and Pyston and Stackless are unmaintained.

See Also