Mental programming: I write less when I can hold the system in my head
Owen MacAllister
September 18, 2026
The code is the easy part. I have known that since the first time I opened an editor with a clear picture and closed it two hours later with a small diff, and the first time I opened it with no picture and closed it two days later with a large one that still missed the failure.
Mental programming is the unfashionable name I use for building that picture before I type. Not a 40-page design. A model I can walk while the kettle boils: who writes, who reads, what must stay true, what happens when the webhook is late. When I can hold that, I write less. Agents made writing even cheaper. They made a missing model more expensive, because they will happily emit the large diff for me.
What I am trying to hold
Not every class. Not the framework. The spine:
- The nouns the business already argues about, and which module owns them.
- The write path — one, if I can help it — and the side effects it is allowed.
- The failure: timeout, double delivery, empty cache, a deploy mid-batch.
- The data that is source of truth versus the data that is a cache or a view.
- The person who will be paged, and what they will need to see.
If I cannot say those out loud, I am not ready to generate code. I am ready to read, or to talk, or to draw on a scrap. I used to skip this because I wanted to look fast. I was fast at producing files. I was slow at producing a system.
The picture does not have to be original. It often already exists in a runbook, a test name, a grim Slack thread. My job is to load it, not to invent a parallel one in a new package.
How I load the picture
I start with a walk, not a diagram tool. I open the last incident, the last migration, the test that names the rule. I write a ten-line narrative in a scratch file: “A payout is created… it can be locked… a webhook may arrive twice…” If I cannot write the narrative, I do not know the system yet. The scratch file is not the spec. It is proof I can speak.
I draw only if the walk forks. Boxes on paper. Arrows that are writes, not “uses.” I throw the paper away when the PR lands, unless the picture belongs in an ADR. I have a graveyard of Miro boards that outlived their usefulness and became a second lie.
I talk to the person who last got paged. Five minutes. “What do people still get wrong?” That sentence loads more model than an hour of reading services I already wrote.
I do not start with the agent. If I start with the agent, I get its picture, which is a collage of files it could find. I load first. Then I use the agent as a junior who implements a picture I can already defend.

When the picture is too big
A million-line frontend or a 200-service mesh will not fit. I do not try. I hold a slice: this journey, this bounded context, this on-call. The rest is a directory I refuse to rewrite from memory. Mental programming at scale is the discipline of not holding the whole company in your head and then making decisions as if you did.
I write the slice boundary down. “I am not thinking about catalog today.” That sentence keeps me from a helpful refactor in a folder I cannot load. Agents love those refactors. I have started saying the boundary in the job file so the model cannot be helpful across it.
If the slice will not load after an hour, the system is under-named or over-coupled. That is a design problem. I do not solve it by typing faster. I solve it by a rename, a test, or a conversation. Sometimes I solve it by walking away until tomorrow. Sleep is a compiler for the picture. I mean that without mysticism. I have woken up knowing the missing failure. I have never woken up knowing a missing import.
Writing less
When the picture is loaded, the diff is usually small: a branch, a constraint, a test, a log line. I delete the speculative helpers I would have written while I was thinking in the editor. The editor is a bad place to think about the whole path. It keeps offering completions for the local file.
I still explore in code when the unknown is local — an API shape, a library I have not used. I spike behind a branch. I do not spike the ledger. The ledger needs the picture first.
TDD, when I use it, is a way to pin the picture, not a way to discover the product. The cases come from the narrative. The red test is a check that I told the truth. If I cannot name the cases, I do not have a picture. I have a hope.

What I tell people who feel slow
Sitting with a closed laptop looks idle. Generating a 30-file PR looks productive. I have been managed by people who could not tell those apart. I now tell my own leads: the idle hour is the work. The PR is the printout.
If you are junior and you cannot hold the system yet, that is expected. Your job is to load a smaller slice and to ask the person who holds more. Pairing is mental programming with a second skull. I would rather pair for thirty minutes than generate for three hours into the wrong module.
If you are senior and you cannot hold the slice, the slice is lying or you are tired. Take the walk. Do not perform speed.
Tools: a notebook, a scratch file, the last incident, the glossary, a walk. I do not need a second brain app. I have used Obsidian when the notes were already there. I have also used the back of a standup printout. The medium is not the method. The method is refusing to type until I can tell the story of the failure.
What I refuse to call mental programming
I refuse architecture astronomy — holding twelve future services in my head so I can feel senior. That is anxiety, not a model. I refuse memorizing the framework’s internals as a substitute for the domain. I refuse a personal wiki that is not tied to a decision I will make this week. Those artifacts feel like loading and are actually stalling.
I also refuse the myth that great engineers do not write things down. The scratch narrative is writing. The test name is writing. “It’s all in my head” is how bus factor becomes a personality. I hold the picture so I can print a small piece of it. I do not hold it so I can be irreplaceable.
Pairing, rubber-ducking, a voice note on a walk — all of those are loading. Doomscrolling the codebase is not. I have confused the two. The tell is whether I can tell the failure story at the end of the hour. If I cannot, I was browsing. Browsing is not programming, mental or otherwise.
A week the picture saved
We were asked to “just add a retry” on a settlement call. The picture, once I loaded it, said the call was not idempotent and a retry would double-send. The job became an idempotency key and a unique constraint, then a retry. The generated-first version I saw in a spike was a for-loop and a sleep. It compiled. It would have been a finance incident. The extra hour with a closed laptop was the cheaper path.
I write less when I can hold the system in my head because the system is mostly decisions, not syntax. Syntax is cheap now. Decisions are not. Mental programming is how I keep the cheap thing from deciding for me.
Load the spine. Name the failure. Write the small thing. Let the agent fill the boring middle only after the spine is yours. If you cannot hold it, do not write more — load better. That is the whole practice. It looks old. It works in 2026 precisely because the new tools will write as much as you allow. I allow less, on purpose, until the picture is there. Then I allow a lot, in the place the picture already pointed.
The code stays easy. The picture is the job. I would rather be judged on the picture. The diff is just what it looks like when I finally sit down. If a lead wants a metric, give them escaped defects on work that skipped the narrative. I have that number. It is not zero. It got smaller when I started closing the laptop on purpose. That is the only productivity story I still believe.