July 26, 2026 Hunter McGuire

You don't need PSRAM

If you are writing MicroPython, that is. We put the Tendrl client on four boards with no extra memory soldered on, including the newly supported Raspberry Pi Pico W. The chip you buy matters far less than how the code gets onto it, and one result went the opposite way to what we expected.

The reflex when a MicroPython project feels tight on memory is to buy the variant with PSRAM: a few extra dollars for a chip with external RAM attached. We wanted to know whether that's actually necessary for a connected device, so we measured it properly: one script, same method, four boards, no PSRAM on any of them.

Worth being precise about what's being measured, because it's the whole basis of everything below. Every figure here is MicroPython's heap, the memory its runtime hands your Python code, not the raw SRAM on the chip. The interpreter, the networking stack and the firmware all take their share first, and what's left is what you actually get. A C or ESP-IDF project on the same silicon has a completely different budget, and none of these numbers transfer to it.

The short answer is no. Every one of them ends up with between 108 KB and 147 KB of free heap with the full client loaded and ready to publish over TLS. That's a lot of room for MicroPython code on a device whose job is to read a sensor and report it.

The longer answer is more interesting, because the biggest single factor turned out not to be the board at all.

Four boards, measured the same way

ESP32-C3 ESP32-WROOM Pico W ESP32-WROVER
RAM on the chip 400 KB 520 KB 264 KB 520 KB + PSRAM
Free at boot 172.6 KB 160.7 KB 182.4 KB 1.99 MB
Free after install 147.0 KB 134.4 KB 107.9 KB 1.96 MB
What the client holds 25.6 KB 26.3 KB 74.5 KB ~26 KB
Typical price ~$2 ~$4 ~$6 ~$6

Two things stand out. The WROVER with PSRAM has nearly two megabytes free and the client's footprint is a rounding error, which is the point: if you buy PSRAM, memory stops being a question at all. But look at what the boards without it manage.

Then look at the last row. Three of those four hold the client in about 26 KB. The Pico W holds it in 74.5 KB, nearly three times as much. That isn't because the Pico W is a worse board. It's because it can't use the install path the other three do.

The install path matters more than the chip

There are three ways the client can end up on a board. We measured all three on a single ESP32-C3 (same board, same firmware, same code), with the install method as the only variable:

Python source Precompiled bytecode Runs from flash
Load time 2420 ms 589 ms 275 ms
Peak while loading 100.5 KB 82.7 KB 32.9 KB
Held afterwards 56.3 KB 73.9 KB 25.6 KB
Free for your code 116.1 KB 98.5 KB 147.0 KB

The third column is the one worth understanding. On ESP32 boards the client can be written to a read-only flash partition and executed in place: never copied into memory at all. It loads nearly nine times faster than source, takes a third of the peak, and leaves nearly 50 KB more free than an ordinary install on the same board.

That ~26 KB figure held within a kilobyte across the C3 and the WROOM (boards with 173 KB and 161 KB free), and the WROVER, on the same ESP32 silicon, lands in the same place. The cost is a property of the software, not the hardware.

The other half of the pattern is just as tight. We also measured two OpenMV camera boards, which can't use flash execution either: the AE3 holds the client in 72.1 KB and the N6 in 72.7 KB, against the Pico W's 74.5 KB. That's three completely different chip families (Alif, STM32 and RP2040) landing within 3 KB of each other. Which install path you use decides the cost; the board barely participates.

The surprise: bytecode isn't a memory saving

We went in expecting precompiling to save memory. It's the intuitive result: ship bytecode, skip the compiler, use less RAM. Measured on one board with the install path as the only variable, that's not what happens.

Precompiled bytecode loads 4× faster and needs a smaller spike while loading, and that spike is what decides whether a small board can install at all. Both are real wins. But the loaded bytecode holds more memory afterwards than the same code compiled from source: 73.9 KB against 56.3 KB. An 18 KB loss, in the opposite direction to expectation.

The mechanism is that neither one avoids the heap. A filesystem is not memory-mapped, so the code lands in RAM whichever form it arrives in; precompiling changes how it gets there, not where it ends up. Only flash execution changes that, which is why it's the column that moves the resident number.

Shipping bytecode is still the right default, for the speed and the peak. It just isn't where the memory saving comes from, so if you're choosing an install method with memory in mind, choose flash execution when your board supports it.

What about the Pico W?

It's new to this list, as of this week a supported board, and the first outside the ESP32 and OpenMV families. It publishes and subscribes over MQTT with TLS, transfers files, and works with the same browser device console as everything else. 107.9 KB free for your application is perfectly workable.

It has one limitation worth knowing before you buy for it, and it isn't about memory: it can't run our local database. That's built on a component compiled into MicroPython itself. Every ESP32 build has it and nothing else we've tested does: not the Pico W, and not the OpenMV cameras either, on two more distinct chip families. Messages written while the network is down still buffer in memory and flush on reconnect, but they don't survive a power cycle, and there's no on-device history or querying. A Pico W reporting readings from a bench or a wall socket is fine; one on a solar panel where power comes and goes is not the right call.

So when do you actually want PSRAM?

When you're moving pictures. Camera work, video streaming, and on-device machine vision need buffers measured in megabytes, and no amount of careful accounting gets you there on a few hundred kilobytes. That's what the OpenMV boards are for.

There's a second case, narrower and easier to miss: holding a working set in RAM. Our local database, MicroTetherDB, can keep its store in memory rather than on flash, and what bounds it isn't the records — sixty of them cost 11.4 KB — but the reads. A query costs about 23 KB of transient heap at 60 records and ~40 KB at 120, near enough regardless of how many rows come back, because the scan is sized by the database and not by the result. On a no-PSRAM board that is a real fraction of everything you have left, so you design around it. On the WROVER it lands in 1.96 MB and stops being a design decision.

Being precise about what PSRAM buys there: not the database itself. That's the btree question from the Pico W section, and it's decided by the port rather than by how much memory is attached — which is why a board with megabytes of RAM on another port still can't run it, and a $2 C3 can. Next post: half an hour of history on a $5 XIAO ESP32-C3 with no PSRAM, and what designing around that 23 KB looks like in practice.

For the thing most connected devices actually do (read a sensor, send a value, take the occasional command), none of these boards is short of room. The tightest leaves 108 KB free; the roomiest without PSRAM leaves 147 KB.

If you're buying for a new deployment, the ESP32-C3 is the one we'd point at: around $2, the most free memory of any no-PSRAM board we've measured, and it runs everything including the local database. Buy PSRAM when you have a reason to, not as insurance.

Every figure here comes from one script run the same way on each board, and you can reproduce it on your own hardware: it ships in the SDK repo as tests/measure_board.py. Per-board results, the method, and a feature-by-feature comparison of every supported board are in the supported boards docs.