FAQ¶
Common questions, grouped by audience. Q1–Q4 are for web developers and embedded/systems developers (the "should I use this?" questions). Q5–Q9 are technical context. Q10–Q13 are about the project itself (mostly for contributors).
Q1: Why "nanopython"?¶
The project started as a strangler port of MicroPython using the nano config preset: the minimal feature set. The codename stuck. It reads "small," though the size positioning has shifted: the wasm shipping artifact is 270 KB today, and the binding constraint is cold-load budget rather than bytes-on-disk. Renaming has come up; the name stays for now.
Q2: How does this differ from porting CPython to Zig?¶
We didn't port CPython. We ported MicroPython, which is itself a smaller, simpler Python implementation written by Damien P. George and contributors over the past decade. CPython is ~600,000 LOC of C; MicroPython is ~53,000 LOC. CPython supports threading, async, hundreds of stdlib modules, the C extension ABI, GIL semantics, free-threading; MicroPython supports a useful core subset.
So this is "MicroPython in Zig" rather than "Python in Zig." The bytecode is MicroPython's .mpy v6. The VM is MicroPython's opcode set. The behavior matches MicroPython byte-for-byte on 541/541 of MP's basic test suite.
Q3: Why Zig instead of Rust?¶
Three reasons:
-
zig cc: Zig ships a C compiler. We could compile MicroPython's C package with Zig's toolchain, drop in Zig modules one at a time, and have a single build system. Rust would have required separate cargo + a C build system. -
Manual memory management. MicroPython is GC'd at the Python layer but the C layer is
malloc/freeplus a custom bump allocator. Zig's allocator-aware std lib matches that shape closely; Rust's ownership model would have required wrapping everymp_obj_tinunsafe. -
Smaller binary. Zig's
ReleaseSmallproduces smaller code than Rust'sopt-level=zin our experience.
Rust would also work. The choice was pragmatic.
Q4: Is it faster than MicroPython?¶
In the same ballpark. The latest benchmark snapshot (benchmarks.md) puts nanopython at 1.28× geomean vs MicroPython unix-standard and 1.96× geomean vs CPython 3.14 across 6 perf_bench programs. Per-benchmark spread is wide: strops is faster than MicroPython (0.79×); sieve is slower (2.04×). The geometric mean sits in the green band per ADR 004 §D8.
The interpreter is mostly the same code shape (Zig fns that mirror MicroPython C fns), so the cross-comparison reflects compiler codegen quality more than algorithmic differences. There's no dedicated perf optimization arc yet; size is the prior cross-cutting goal.
Q5: Can I use this in production?¶
For browser use cases, increasingly yes. The wasm artifact ships at 270 KB, the integration suite + corpus harness both run green on wasm, and the JS bridge (ADR 006) handles DOM access. The remaining gaps are documented in COMPATIBILITY.md; the headline blocker is the eval/exec longjmp wall on wasm, closing in v0.6.
For native/embedded systems use cases, also increasingly yes. The build cross-compiles to x86_64 / arm64 macOS + Linux and to wasm32-wasi. The production caveats sit around process maturity (no CI yet, no formal release process, solo-maintained) rather than around interpreter behavior. On 541/541 of MicroPython's basic test suite, native nanopython is byte-identical to MicroPython unix-standard.
Q6: Does it work in a browser?¶
Yes: wasm is one of two co-primary distributions (64-bit native is the other). make web produces web/nanopython.wasm (270 KB) and the bundled demos under web/ show it driving the DOM via the js module. The cold-load story is "load and start running in well under a second on a typical connection"; longitudinal measurements are recorded via make metrics-record. wasm32-wasi is the current wasm target; wasm64 (memory64) is the strategic wasm target, gated on Zig toolchain support.
The path was unlocked by the strangler's NLR boundary work: MicroPython's setjmp/longjmp machinery (absent from native wasm) was replaced with Zig's error{PyRaise}!T error union. Once that landed, wasm targeting became straightforward.
Q7: Why is the native Debug binary 3.5 MB?¶
Zig's Debug mode keeps every function alive (no DCE) and emits full DWARF debug symbols. That's the right tradeoff for development: you want stack traces when things crash. For shipping, use make build-small:
The DWARF symbols are stripped automatically in Release modes.
Q8: Why is the corpus 541/541 instead of "all 541"?¶
It is "all 541" now. The denominator (541) is the in-scope test count; the numerator (541) counts tests that pass byte-identical to the frozen reference. The three pre-existing gaps that were listed for years (builtin_help, gc1, string_strip) closed in the 2026-06-11 session, reaching the long-standing strangler invariant. See the v0.5 retrospective in ADR 009 §11.
The wasm arm reports 472/541 raw with a 68-entry documented baseline (corpus/wasm-known-fails.txt). The wasm-known-fails entries are explicit deferrals (bignum tests blocked on MPZ, eval/exec tests blocked on the longjmp wall, two deque_* tests blocked by a deliberate CPython-compat API divergence). The wasm gate is strict in both directions: a new fail not on the list and a now-passing test still on the list both fail the gate.
Q9: How long did the strangler arc take?¶
Roughly 6 months part-time, ending in mid-2026. The shape was: ~6 months of careful preparation (shaping the codebase into independently-portable units), then a focused finishing arc that closed the long tail.
TR-001 (the methodology paper) has the quantitative breakdown. See §4 (case study) and §4.4 (the cross-target bug class that surfaced specifically because the work hit a real wasm target).
Q10: What about microcontroller ports (STM32, ESP32, RP2040, etc.)?¶
Out of scope. MicroPython's home turf is bare-metal embedded; nanopython's distinguishing value is the wasm story. We're optimized for a different cold-load constraint, a different sandbox, a different user. If you need Python on a microcontroller, use MicroPython: it has dozens of mature ports + a community + ecosystem.
The native binary's scope is "Linux/macOS scripting target + development companion." Microcontroller targeting is a separate (large) arc that the project hasn't taken on.
Q11: How do I report a bug?¶
If make drops below the documented gates on your machine, that's a bug. Steps:
- Run
make verifyto identify which corpus test regressed. - Open the failing
.pyfile undersandbox/micropython/tests/basics/and the corresponding.outgolden incorpus/nano/basics/. - Diff the binary's output against the golden:
- The diff names what changed.
For wasm-specific bugs, make wasm-check produces the same kind of pass/fail list (filtered against corpus/wasm-known-fails.txt). For "the binary started but crashed" type issues, the integration suite (tests/integration.py) is the right place to add a regression pin.
Q12: Is this trying to compete with MicroPython?¶
No. nanopython is a strangler-pattern experiment with a useful artifact at the end. The output (a small Python interpreter that runs in the browser) is useful in its own right, but the project is primarily an exercise in incremental rewriting under behavior-preserving oracles. The methodology paper (TR-001 (notes/tech-reports/TR-001.md)) is the publishable form of the lessons.
If you want a production-grade small Python implementation for embedded work, use MicroPython. If you want Python in a browser without 30 MB of Pyodide, nanopython is a credible option.
Q13: Why so many docs?¶
Because the project is a journey more than a product. The interpreter at the end is small; the discipline that produced it is the interesting part. Future readers benefit from understanding the discipline alongside the artifact.
For a quick answer: the README is the short version, and COMPATIBILITY.md is the "does my code work?" answer.