Your Working Memory Has a Budget, and Your Code Is Spending It
The bottleneck in software isn't the compiler or the network. It's the four or so things a human can hold in mind at once. Good code is memory management for people.
Watch an engineer read unfamiliar code sometime. Really watch. They scroll up, scroll down, open a second file, mutter a variable name under their breath, put a finger on the screen — an actual finger, on an actual screen — to hold their place while they chase a definition. What you're seeing is a cache eviction. Their working memory just overflowed, and the finger is swap space.
I find this endearing and also deeply instructive, because it points at the real bottleneck in software development. It isn't compile times, and it isn't typing speed — a question worth revisiting now that AI assistants have made typing speed effectively infinite. The bottleneck is the human head. And the human head has a hard, non-negotiable budget.
The bottleneck is not the compiler
Cognitive psychology has been circling this number for decades. George Miller's famous 1956 paper gave us "the magical number seven, plus or minus two" — the rough count of chunks a person can hold in working memory. Later work, notably Nelson Cowan's, revised the estimate downward: for novel, un-chunked material, the realistic figure is closer to four.
Four. That's the register file you're programming against every time you write a function some future human must read. Every live variable, every pending condition, every "remember, we're inside the retry branch" — each one occupies a slot. When the slots run out, the reader doesn't gracefully degrade. They thrash: re-reading, backtracking, losing the thread, deploying the finger.
The escape hatch is chunking. An expert chess player doesn't see thirty-two pieces; they see "a Sicilian, castled kingside" — one chunk. Likewise, an experienced engineer doesn't see twelve lines of iteration and accumulation; they see "a group-by." Good code is code that chunks well. Bad code is code that forces the reader to hold the raw pieces.
Three kinds of load
John Sweller's cognitive load theory, developed for instructional design, carves mental effort into three kinds, and the taxonomy maps onto code almost embarrassingly well.
Intrinsic load is the difficulty inherent in the problem. Distributed consensus is hard. Tax law is hard. No refactoring removes this; it comes with the domain.
Extraneous load is difficulty added by the presentation: the misleading name, the function that does three things, the boolean parameter that inverts the behavior, the abbreviation only the author understands. This is the load we impose on readers through our choices — and it is entirely ours to eliminate.
Germane load is the productive effort of building understanding — forming the schema, learning the domain. This one you want readers to spend their budget on.
The craft of writing maintainable code reduces to a single directive: spend the reader's budget on intrinsic and germane load, and drive extraneous load toward zero. When people say a codebase is "hard because the domain is hard," it's worth asking honestly which kind of hard they mean. In my experience, teams routinely blame intrinsic load for what is mostly extraneous load with good lawyers.
Deep modules, shallow modules
The best architectural framing of this idea comes from John Ousterhout's A Philosophy of Software Design, and it's the distinction between deep and shallow modules.
A deep module has a small, simple interface concealing substantial functionality. The canonical example is the Unix file API: five-ish calls — open, read, write, seek, close — hiding decades of buffering, caching, permissions, and device drivers. The interface is the part that occupies your working memory; the implementation, in the happy case, occupies none of it. A deep module is a great deal: you pay one chunk and receive enormous capability.
A shallow module inverts the bargain: an interface nearly as complex as what it hides. The wrapper that adds a parameter and delegates. The "helper" that requires you to read its body to know what it does. The class whose seven configuration flags interact in ways only the source reveals. Shallow modules are worse than no abstraction, because they add a layer of indirection — a slot in your budget — without absorbing any complexity in exchange.
Here's the uncomfortable corollary: lots of small classes and functions is not automatically a virtue. If splitting a function scatters one coherent idea across six locations, the reader now pays for six chunks plus the navigation between them. The goal was never small pieces. The goal is cheap chunks — units that can be understood once, trusted, and then held as a single token.
Names and functions as memory management
So what does managing the reader's memory look like at the keyboard?
A good name is a free chunk. daysUntilExpiry costs nothing to hold; d2 costs a lookup every time doubt creeps in. Names are how you let readers cache conclusions instead of re-deriving them.
A function boundary is a checkpoint. When a reader finishes validateShippingAddress(order) and trusts it, they get to flush everything about validation and carry forward one bit: it's handled. This only works if the function honors its name completely — a validator that also mutates the order picks the reader's pocket, because now nothing can be safely flushed.
Locality is mercy. Every variable that lives for forty lines is a slot occupied for forty lines. Declare close to use, keep live ranges short, and let scopes end so the reader's memory can too.
Consistency is compression. When every service in the codebase follows the same shape, the shape itself becomes one learned chunk. Novelty is a tax; spend it only where the problem is genuinely novel.
And a note for the current moment: AI-generated code tends to be locally fluent and globally unchunked — plausible line by line, but indifferent to the conceptual boundaries that make code holdable in a head. The assistant doesn't pay your reader's memory costs, so it doesn't economize on them. That's now your job in review: not just "is it correct?" but "does it chunk?"
The seminar summary
Takeaways for the week:
- Count the slots. Before merging a function, tally what a reader must hold at its most crowded point — live variables, open conditions, implicit context. More than about four, and you're over budget. Extract, rename, or restructure until it fits.
- Audit one wrapper. Find a shallow module you own — an interface as wide as its implementation — and either deepen it or delete it.
- Rename one
data. Somewhere in your codebase is adata,info,temp, orresult2that forces every reader to reconstruct its meaning. Give it its real name. - Ask the chunking question in review. Alongside "is this correct?", ask "what must I hold in my head to read this?" If the answer is "the whole file," say so — kindly, and with a suggestion.
We spend so much effort scaling systems. The scarcest resource in the building is still the four slots behind each pair of eyes. Budget accordingly.