Complexity is not where you think: why complex code costs nothing until someone reads it
Two engineers look at the same screen. On it, a freshly opened diff: a function that now takes a configuration object instead of five separate arguments, a cache that came to be shared across three modules that never spoke before, a boolean flag deciding whether the discount applies before or after tax depending on which country the process runs in. The first holds that it is fine, that it is simple, that anyone gets it in two minutes. The second objects that it is a mess, that in six months nobody will be able to touch that function without breaking something somewhere else.
The two are looking at exactly the same code. Neither lied, neither saw something the other did not. The same lines, the same screen, the same commit. So the question left is not who is right, but something more uncomfortable. Is complexity in the code, waiting to be found by anyone who can read it? Or is it in the head of whoever reads it, and what changes from one engineer to the other is the head, not the code?
The word comes already split
Before arguing which of the two engineers is right, look at the word both are using. The dictionary gives two senses of “complex” that do not point to the same place. The first is structural, almost geometric, made up of many connected parts, composite. The second is of another nature, complicated, not easy to analyze or untangle. One describes a property of the object, how many distinct parts form it; the other describes a relation with whoever faces it, how much it costs to untangle. The word itself does not decide between the two. And that indecision is not a lexicographic oversight. It is, in embryo, the same tension this essay is going to walk end to end. Is complexity something the object has, or something that happens to the subject?
The Latin root of the word hints at yet another thing, the idea of binding or braiding one thing with another, but we will come back to that later.
That a single word should carry two meanings that do not coincide is a perfect example of the ambiguity of natural language, and that ambiguity is itself a source of complexity. Two people say “complex,” believe they are saying the same thing, and yet one speaks of structure and the other of difficulty. They never collide head-on, because the word covers them both. The same thing happens to the two engineers over the diff. In an earlier piece I wrote about ubiquitous language in domain design, where the problem had the same root under another disguise. Code and business used the same words with different senses, and the debt piling up was not technical but semantic. Before deciding where complexity lives, we have to accept that the word we name it with is already split in two.
Each half of the word gets its own school of thought. And they are opposites.
Code as object
For Rich Hickey, who set out this position in his talk “Simple Made Easy,” the answer is the first. Complexity does not depend on who looks; it is a property of the artifact, as verifiable as the weight of a stone. It comes from the etymology of the word itself. “Complex” descends from the Latin complexus, from complector, to entwine or fold threads together, com- plus plecto, to weave or braid; “simple” comes from sim-plex, one fold (this derivation is a contribution of this essay, not of the talk that inspires it). Braid and fold are the same gesture seen from different angles. The simple is a single fold; the complex is several folded over one another. Hickey himself puts it more directly, calling it “to interleave or entwine or braid… complecting.” Complexity is entanglement. Simplicity, by contrast, is having one role, one responsibility, one concept tied at a time.
What is interesting about this position is not the textile metaphor but the epistemological claim that comes with it. For Hickey, if two concepts are entwined in the code, you can go, look, and verify it, with no need to ask anyone how it feels to read. State that holds both a value and its history of changes at once is entwined, and that braid lives in the structure of the artifact itself. The load that entanglement produces is, on this thesis, combinatorial. Each concept added to the braid multiplies the ways you have to think about the whole; it does not add them.
This places complexity on the side of the object. The two engineers’ diff, under this lens, has a single answer independent of who reads it. If the country flag entwines the discount logic with the tax logic, it is complex, whoever says otherwise. But that claim leaves a question open. If complexity belongs to the object and not the subject, why does the second engineer need to look longer, and the first does not?
Code as limit
The opposite school answers that question. Edsger Dijkstra, in his essay “On the role of scientific thought,” decades before software engineering as we know it existed, had already identified the real bottleneck. It was not the machine but the head of whoever programs. “Compared with what could be known, we have very, very small heads.” Separating concerns, for Dijkstra, is not one design technique among others but the only way he knew to order thought effectively. Complexity, on this reading, does not live in the code. It lives in the distance between what a finite skull can hold at once and what the problem demands it hold.
John Ousterhout took up that idea in A Philosophy of Software Design and made it operational. He defines complexity directly in terms of the reader, as “anything related to the structure of a software system that makes it hard to understand and modify the system.” There is no objective property independent of that “hard to”; the difficulty is the definition. Ousterhout names two concrete causes of that difficulty. One is dependency between parts that ought to be changeable separately. The other is what he calls “information leakage,” when a design decision leaks out and ends up reflected in several modules at once, forcing whoever changes one to remember the rest. And he warns of something that sits badly with the “we’ll fix it later” intuition. Complexity is incremental; it builds up from small decisions, none of which seems, on its own, like much.
Read this way, the second engineer over the diff is not seeing something different from the first. He is seeing the same thing, but paying a cognitive cost that the first, through prior experience with that module or plain luck, does not pay yet. Complexity, here, is a limit of the subject, not a mark on the object.
Parnas’s hinge
David Parnas, long before either of them, had already found the move that connects the two positions without settling the dispute between them. In 1972, in “On the Criteria to Be Used in Decomposing Systems into Modules,” he proposed abandoning the habit of decomposing a system by its flowchart. Instead, decomposition “begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others.” Parnas called this information hiding, and it is the exact cure for Ousterhout’s leakage. A decision that stays hidden cannot leak to other modules. Hiding it serves, at once, two functions that until now seemed to belong to opposing camps.
On the object’s side, hiding the decision keeps it from entwining with the rest. It is Hickey’s gesture, unnamed. A hidden decision cannot be braided with another that never touches it. On the subject’s side, hiding the decision reduces what you need to hold in your head to work with the rest of the system. It is the same operation as Dijkstra’s and Ousterhout’s. There is less to remember, less chance the finite head overflows.
Parnas shows that both schools converge on the same remedy, without explaining why something can be, at once, a property of the code and a limit of whoever reads it. That why stays open.
What the fold does not cost until someone unties it
Here it helps to concede something first. Hickey is right. Entwining exists; it can be pointed at. But there is a second observation, so simple it is easy to overlook. An entwined artifact that no one reads costs nothing. The fold is there, it is real, but it is inert. Nobody pays for it until someone tries to untie it.
The cost of complexity does not live in code at rest. It lives in the act of understanding it.
That cost switches on in the three symptoms Ousterhout describes. It shows up when you have to modify the system in several places at once, when you need to load more context into your head than fits comfortably, when it is unclear what other piece also has to be touched. None of those three exists in the static text of the program. They exist when someone uses it to think, to change, to extend.
Two ideas from the philosophy of language help sharpen the point, read here with some freedom. Gottlob Frege distinguished between the reference of an expression (what it points to) and its sense (the way it is presented). In software, this means two versions of the same function can have identical observable behavior, the same reference, and yet present themselves in radically different ways to whoever reads them. A refactor that preserves behavior changes the mode of presentation, not what the function does. That is why the argument “who cares, it does the same thing” is not enough to defend entwined code. It can be true of the reference and false of the sense, and the sense is exactly what determines how much it costs to understand.
Ludwig Wittgenstein, for his part, held that the meaning of an expression lies in its use, not in some fixed property it possesses in advance. Carried over to code, you cannot judge how much a module costs by reading it at rest. It reveals itself when you modify it, when the change amplification and Ousterhout’s “unknown unknowns” appear or fail to appear. Understanding a system is not a private mental state occurring inside Dijkstra’s finite skull. It is a public practice, something done with the code, among people, over time.
There it is, the thesis. Entanglement is objective — Hickey wins that part of the dispute — but its cost does not exist until it is measured in understanding, and understanding is use.
The cost belongs to use
Let us return to the two engineers’ diff. Neither was wrong, and they did not need to agree while staring at the screen in silence. The flag that entwines the discount with the tax by country is, objectively, a braid in the code, whether or not anyone is looking at it right now. But that braid costs no one anything until someone needs to extend it, fix it, or explain to another person why it exists. There, and only there, the fold becomes a bill.
Alan Kay, in his 1997 OOPSLA talk “The Computer Revolution Hasn’t Happened Yet,” described systems built without any shared practice of understanding through the image of an Egyptian pyramid raised by brute force, millions of blocks stacked one on another with no structural integrity. Brute force is what is left when no one else can read what you entwined.
Against that pyramid, Kay proposed organizing software the way life organizes matter, in cells that hide their interior behind a membrane and communicate only through messages. Cells scale by factors of a trillion while a pile of blocks collapses, through organization and not accumulation. His rule is a single one.
You must not let the interior of any one of these things be a factor in the computation of the whole.
Not letting the interior of an object weigh on the computation of the whole is taught as a technique and is much more than a technique. It is called encapsulation, and it is not hiding the attributes behind a getter that exposes them anyway. It is containing complexity behind a boundary, so that the interior of one part does not leak into the rest. It is the only way we know to make the complex tractable, and it is what Parnas’s information hiding, the membrane of Kay’s cell, Hickey’s refusal to entwine, and Dijkstra’s separating all found by different roads. Kay invented none of these ideas. Parnas and Dijkstra are his contemporaries; Hickey came much later. His own contribution was to gather them and turn them into architecture, though not the OOP of classes and inheritance most people picture when they hear OOP. For Kay the center was never the object but the message, so much so that he came to regret the word he had coined himself, for pushing everyone toward the lesser idea. None of them, at bottom, was talking about code. They were talking about where to place the cuts.
And where those cuts fall decides who pays. The cost of living with them is never carried by a single head. It spreads across the team, is inherited from whoever wrote before, is settled with every pull request. A fold its author understood without effort can become an unpayable bill for whoever inherits it six months later. That is what complexity truly costs. Not what the code entwines, but what that entanglement demands of the shared practice that has to keep reading and changing it.