Notes
2026/06/20

Natural limits to complexity

Limiting complexity in software is hard. We know that apex predator of grug is complexity, but how do we fight complexity?

Throwing away code at the end of the day

In his talk at HYTRADBOI 2025, Tyler Neely discusses the idea of throwing away anything that you can't finish in a day (and starting over the next day) as a natural limit to complexity, an idea that originated with Joe Armstrong. I previously wrote about it and used it for a few programs. It's a really interesting way to approach software, you should try it out if you haven't.

What are you supposed to throw away and rewrite from scratch though? A function? A module? Your entire program? Your entire program and all of its dependencies? “If you wish to make an apple pie from scratch, you must first invent the universe.”

Given that the idea of throwing away code and rewriting it seems to have originated in the context of Erlang, maybe the natural system boundary is an actor? That would turn it from a mostly social principle of limiting complexity into one that is intimately tied to an engineering context: Your (programming) language shapes your system boundaries and thus determines what and how easily you can throw away pieces of code. (Would effect handlers be another example of a language feature that makes it easier to separate components and throw away code?)

Making code a part of your output

Another way of limiting the growth of code (and thus putting a boundary on its complexity) is through making the code a part of its output, as a quine (or more generally any program that has to visibly carry around its source code).

Normally, the source code of a program is seen only as the recipe for creating the eventual output, not as part of the output itself. The code is then only a means to an end and there is nothing preventing the code from growing and growing, as long as the output justifies it (and the end justifies many questionable means).

While it is possible and even easy to construct an arbitrarily large quine, the fact that the code appears as part of the output, exhibits itself as part of the output, puts a natural limit on how large it can grow before becoming uninteresting to look at. A quine with millions of lines of code is not aesthetically pleasing as a quine anymore.

Dogfooding code by compiling the compiler

What if (when building a compiler) you only kept changes that made the compiler faster at compiling itself? That's exactly the approach followed by Niklaus Wirth for the Oberon compiler. Most production-grade compilers are complex beasts that care primarily about making programs faster, which favors adding more and more compiler optimizations. But what if every compiler optimization has to pay for itself?

There is a parallel to quines here, in that both a quine reproducing itself and a compiler compiling itself are about dogfooding, about referencing itself. This makes complexity apparent, instead of letting code grow and fester because only the (non-code) output is what counts.

Is there a connection between all three approaches other than limiting complexity somehow? Maybe in the sense that every approach is about rejecting the unbounded accumulation and growth of code, through building explicit feedback loops into the process. Feedback loops designed to reject and sacrifice code that doesn't meet a meta standard, in that it introduces the notion of building the code into the output of the code itself.