The Fastest Python Struct?<br>CrumpledPaper
The Fastest Python Struct? JP Hutchins The Fastest Python Struct?<br>June 21, 2026 · 34 min read<br>types<br>python<br>CPython<br>struct<br>mypyc<br>functional programming
All posts written without LLM assistance unless otherwise noted.
Python is fast enough. Python programmers tend to understand the Python Cost Model, Python’s strengths and weaknesses, libraries that give compiled performance, and when to use a compiled language from the start.
So why do I care? Why do I get obsessed enough to coerce Claude into running these<br>benchmarks and writing these Plotly charts? I do not know.1
But! I do know what I care about (for now) - and today (and some of the past weekend, and perhaps some of the next one),<br>it’s definitely the cost of defining (ideally immutable) record types (AKA structs) in Python .
So let’s get this out of the way: this write up is about benchmarking “Python type speed” (informally: compile-time), it is NOT about benchmarking
serialization
instantiation
attribute access
validation
memory
Right, so that’s what Python programmers often care about, because they are probably working on long running programs, like apps, servers or pipelines, where the cost of defining a type is paid upfront, one time, whereas the cost of allocation, instantiation, validation, and serialization is paid repeatedly. So yeah, if that’s what you care about, this post is not for you.
But I did include instance cost benchmarks if you’re curious. 😻
If you already know you care about type definition speed, then jump straight to the analysis, otherwise keep reading for my motivation and context on this subject.
#”how fast to --help”
I tend to work on CLIs for developers and tooling for build systems or test suites where the time from program start to end is what we’re measuring. Perhaps you’ve noticed that running a command from a CLI may be near instant in a compiled program, but in Python, it can easily be hundreds of milliseconds: perceptible for UX, noticeable in CI/CD, and amplified by repeated calls as part of build system tooling.
Unlike in a compiled language, Python type definitions are not free (free in the sense that they were paid for during compilation ahem, Rust). They are code to be executed on every startup. And that includes imports of libraries and their type trees and dependents trees. We’ll see in the benchmarks that (evil-runtime-) metaprogramming, like decorators, metaclasses, or worse, have more of an upfront runtime type generation cost than manual type definitions.
Can we get the best of everything: a Pythonic type definition style, complete static typing and match, with the speed of a hand-written C struct, and the startup time of a compiled extension? I think so. seriously, I’m not sure, need to do more work, but I have good preliminary data
__pycache__/*.pyc and mypyc
Python is not completely “uncompiled”, and we’ll be assessing both cold (no python bytecode cache) and warm (with python bytecode cache) startup times, and look at how mypyc can help with some of the costs.
But why not use a compiled language and framework like Rust + clap? I certainly do, but what can I say? I love the Python ecosystem, build tooling libraries, and the rapidly evolving type system. And I believe that the type system can continue evolving so that we can offload a lot of the correctness to the type checker, and reap runtime speed benefits. That’s what this post is about.
#OK, OK, whatever, but why “structs”?
I’ll confess that I am an advocate of functional programming (FP), with little compromise. But the tortured kind, that can’t be bothered to learn Haskell, or study Lisp, and seems to end up rewriting the same handful of patterns in every language. So, it’s not the structs alone that I am after. It’s the sum types and pattern matching .
Algebraic Data Types
sum type is the FP term for a type union. enum in Rust, tagged union in C, std::variant in C++, discriminated unions in TypeScript, are common examples. A plain struct (or record, implying immutability) is a product type in FP.
pattern matching is the related control flow aspect. switch in C & C++ do not pattern match (though they can guarantee exhaustiveness at compile time), but match in Rust, Python, and Haskell do.
If you’re not feeling motivated, then this YouTube video by No Boilerplate is a great intro, though you’ll have to extrapolate that this is (more or less precariously) applicable to many type systems, not just Rust - but Rust just did it well ❤️🦀 (for a procedural language 😜).
Long story short, I use sum types and pattern matching everywhere, all the time, from Rust to embedded C, from Typescript to Python, from JSON to CBOR. Even if your not an FP…enthusiast, you’ve likely used them in Python without thinking of them as such, when reaching for MyType | None (an Option or Maybe type).
This example imagines that some immutable device info burned onto a ROM is versioned V1 and V2. V1 guaranteed...