Published Mar 5, 2026

Types Are Not Optional: A Rant in Defence of Static Typing

Strong static types aren't a preference — they're the difference between a system and a pile of guesses.

I have a strong opinion about this and I'm going to state it plainly: writing production software without a static type system is an act of aggression against the people who have to maintain it after you leave. I've spent too many hours reading Python that returns Optional[Union[str, int, None]] from a function named get_result() to pretend otherwise.

Dynamic typing is not freedom — it's deferred debt

The argument for dynamic languages is always velocity: you ship faster when you don't have to declare types. I believed this for longer than I should have. It's true in the first two weeks of a project. It becomes false somewhere around the first time you refactor a core abstraction and realise you have no idea what's calling it or what it expects back.

Static types are documentation that can't go stale. They're contracts that the compiler enforces so you don't have to. Every dict parameter you accept instead of a typed dataclass is a small act of cowardice — you're pushing the verification work to runtime, where it fails at 2am in production instead of at your desk at 2pm during development.

The ML ecosystem is the worst offender

The codebase I see most frequently is ML research code that turned into production ML code without ever being cleaned up. Tensors of unknown shape. Functions that accept either a batch or a single sample but the distinction is handled by a try/except inside. A training loop where the loss variable is reassigned four times in the same scope and you genuinely cannot tell which value is logged.

PyTorch, to its credit, has gotten substantially better at typed interfaces. JAX has jaxtyping. The ecosystem is catching up. But the culture hasn't — there's still a strong association between "I'm a researcher" and "I write untyped Python," as if rigour is the enemy of creativity. It isn't. Sloppy types are the enemy of other people's creativity, because they burn their time just understanding your code instead of building on it.

Go did this right

Go is not a beautiful language. It doesn't have the elegance of Haskell or the expressiveness of Rust. But it has something worth more for most production systems: it's almost impossible to write Go that someone else can't read in ten minutes. The type system is simple and present everywhere. You always know what a function takes and returns. Interfaces are structural and explicit. It's not magic — it's engineering.

I've maintained Go services for three years after writing them. I've never once had to spend an hour rediscovering what a function returns. That's the dividend of boring, explicit types. It compounds.

What I actually do

For Python I use mypy in strict mode from day one and I make it a CI gate. I don't merge code that doesn't type-check. This is not popular. People complain that mypy is slow and annoying and sometimes wrong. All true. It's still better than the alternative. I use pydantic for all data boundaries — anything that crosses a function signature that I don't fully control gets validated at the boundary. Internally I use dataclasses with type annotations everywhere.

For new services, I reach for Go unless there's a specific reason not to. For ML, I type the training loop strictly and leave the research scratch area loose — but I enforce that the boundary between them is typed and tested.

Types won't make you a better thinker. But they will make your thinking visible, and visible thinking is what separates code you can build on from code that slowly suffocates the people who inherit it.