What’s the difference between CPython, MicroPython, & CircuitPython anyway?

CPython, MicroPython, and CircuitPython hold a dear place in the hearts of many a tinkerer. But it’s not always clear what the differences are. And if you’re messing around with microcontrollers for the first time, these distinctions can play a key role in understanding what’s up with your code.

So, in this article, I will explain what these three implementations of Python are and how they compare and contrast.

It’s important to remember that Python is both a language and a program. In this context, the implementation is the program that interprets and executes Python code.

So we’re mostly talking about three programs (which include runtime environments, standard libraries, and other tools) that can run Python code. So let’s start with the reference program – CPython.


CPython is the default and most widely used implementation of the Python programming language. Usually, when people talk about Python, they are talking about Python running on CPython.

Unsurprisingly, it is written in C (hence the “C”) and serves as the reference implementation for the Python language. If you want all the dirty details, you can check out CPython’s documentation here.

CPython interprets and executes Python code and provides the core functionality and features of Python. And since it’s the most used, it also has access to the most extensive ecosystem of libraries and packages, including libraries like NumPy, Django, Flask, TensorFlow, etc.

CPython also has runtime environment with dynamic memory management (including automatic memory allocation through reference-counting as well as garbage collection).

When it comes to error handling, CPython offers you the ability to manage errors and exceptions through try-except blocks.

It also, of course, has a built-in debugger module, pdb, which supports breakpoints and single stepping. You can give it a whirl by running:

import pdb; pdb.set_trace()

Finally, CPython uses a Global Interpreter Lock (GIL), which only allows one main thread to execute Python bytecode within a single process. In other words, CPython supports asynchronous programming, but not parallel programming. More on Async and Parallel programming in an upcoming post!

Some other implementations don’t use the GIL so that they can offer different approaches to concurrency and parallelism.

For example, Jython runs on the Java Virtual Machine.

IronPython runs on .NET.

And PyPy runs on Software Transactional Memory.

CPython, MicroPython, and CircuitPython, however, all run with the GIL in place.


In contrast to CPython, which is designed for general purpose (desktops, servers, embedded systems, etc.), MicroPython is an implementation of Python specifically optimized for microcontrollers (like the Pico!) and embedded systems.

It’s worth mentioning here that MicroPython is sometimes written as uPython, because of the Greek letter μ (“mu”, as in the first letter of “mikrós”).

MicroPython thereby doesn’t require an operating system, you can run it effectively as the operating system on a microcontroller.

Typically, in order to do that, people rely on the Thonny IDE, which has a number of extremely useful tools for dealing with MicroPython.

While still written in C, MicroPython is all about providing a compact runtime for less powerful devices – so it uses less memory and really only provides specific modules and libraries for embedded systems, like machine, utime, and network.

If you’re looking for some awesome MicroPython libraries, you can check out Awesome MicroPython.

CPython uses reference-counting as its main garbage collection algorithm, but MicroPython uses Mark and Sweep as its main garbage collection algorithm to manage memory.

It was created by Damien George 10 years ago and if you want to listen to him talk about it, you can check out this lecture:

So MicroPython doesn’t have all the features of the standard library in CPython.

Unlike CPython, MicroPython doesn’t have built-in support for pdb as a debugging tool. Instead, if you’re using MicroPython, you’re going to need to rely on other techniques like print statements.

If you’re interested in utilizing external debuggers, you could check out this very cool MicroPython Debugger on GitHub. It’s aimed at providing pdb features to MicroPython.

If you’re interested in checking out the MicroPython documentation for more in-depth stuff, you can follow this link to see all the details.

So MicroPython is a more constrained implementation of CPython, but it’s still marvelously powerful and often the ideal tool for many projects.


Another great tool though is CircuitPython.

CircuitPython is a fork of MicroPython associated with Adafruit and is aimed at being more beginner-friendly (whether or not MicroPython isn’t beginner-friendly enough is certainly a matter of debate).

In fact, the differences between MicroPython and CircuitPython are extremely small.

One notable difference is that CircuitPython is designed to have files moved to the board by appearing as an external disk (just as a USB would). The idea there is that you don’t need to upload a file through Thonny (although you still can).

For instance, CircuitPython has specific libraries, such as adafruit_neopixel, adafruit_bmp280, and adafruit_dotstar, which are all aimed at specific hardware.

Something that really sets CircuitPython apart is the vibrant community that Adafruit has fostered around it. You can check out the CircuitPython forum and the discord to see for yourself. (I should point out that MicroPython also has a forum and a discord, albeit far less frequented, since most MicroPython discussion happens on GitHub).

If you want to learn more about CircuitPython and its differences with MicroPython (and CPython), you can check out its documentation page here. Even its documentation looks similar to the MicroPython documentation!

Otherwise, you can check out this video put out by Adafruit:

If there are any other interesting differences you know of, let us know in the comments below!

Leave a Comment