Vibe Coding, Engineering, and the Debt Nobody Invoices
Shipping code you don't understand now takes one afternoon. The bill arrives later, in a currency called comprehension. On Naur, vibe coding, and why 'it works' isn't 'it's done'.
A former student of mine — a good engineer, three years into industry — told me recently, with a mix of pride and unease, about the fastest feature he ever shipped. An afternoon with an AI assistant: OAuth integration, token refresh, retry logic, the works. Demo went beautifully. Then in the design review someone asked, "What happens if the refresh token expires mid-request?" And he realized, standing at the whiteboard, that he didn't know. Not that he'd forgotten — he had never known. The knowledge simply wasn't in the building. It had been generated, tested against the happy path, and merged.
"It felt," he said, "like discovering I'd co-signed a loan I never read."
That's the right metaphor, and I want to give the loan its proper name.
What vibe coding actually is
The term "vibe coding" was coined by Andrej Karpathy in early 2025, and it named something people were already doing: describing what you want, accepting what the model produces, running it, and iterating on the outputs — pasting errors back, nudging with prompts — without ever really reading the code. You surrender to the vibes. The code becomes a kind of weather: you respond to it, you don't comprehend it.
Let me be precise about my position, because this is a topic that attracts both hype and doom, and I have no patience for either. AI assistants are a permanent part of the toolchain now, the way compilers and garbage collectors are. They are genuinely marvelous. And vibe coding, in its place, is a legitimate mode: prototypes, throwaway scripts, exploring an unfamiliar API, learning by generating examples. Karpathy himself framed it around low-stakes weekend projects. For a disposable artifact, comprehension is optional because the future is short.
The failure mode is not vibe coding. The failure mode is vibe coding with a production merge at the end — applying the disposable-artifact workflow to code that will live for years, and telling yourself it's done because it works.
Naur's ghost
The deepest explanation of why this matters was written four decades before the first code assistant. In 1985, Peter Naur published an essay called "Programming as Theory Building," and its central claim is one of the most quietly radical ideas in software: the primary product of programming is not the program. It is the theory of the program held in the programmers' minds — the understanding of how the code maps to the world, why it's shaped the way it is, which changes it will accept gracefully and which will fight back.
Naur's evidence was teams: when the original builders left, their successors — with full access to source and documentation — routinely made changes that were locally plausible and globally wrong, because the code text had survived but the theory had died. Source code, in Naur's framing, is a lossy artifact. It's the shadow the theory casts, not the theory itself.
Now run vibe coding through Naur's lens and the problem snaps into focus. When a human writes code, the theory forms as a byproduct of the struggle — every bug fought, every alternative rejected, deposits understanding. When a model writes code and a human merely accepts it, we get something historically unprecedented: a program that is born theory-less. Not a dead program whose theory was lost — one where the theory never existed in any head at all. The model doesn't retain it; it has no memory of your codebase tomorrow. The human never built it. The code arrives as an orphan.
Comprehension debt: the invoice arrives later
Technical debt, in Ward Cunningham's original metaphor, was a deliberate trade: ship with a suboptimal design, learn from reality, repay by refactoring. The debt was visible to the borrower.
What my student took on was something adjacent but nastier, and it deserves its own line item: comprehension debt — the gap between what your code does and what your team understands about what it does. It has three properties that make it more dangerous than the classic kind:
It's invisible at the moment of borrowing. Design debt announces itself — you know you hacked it. Comprehension debt is incurred at the exact moment everything looks great: tests green, demo smooth, PR merged. The dashboard reads "healthy" precisely because understanding is not on the dashboard.
Interest is charged at the worst times. The debt comes due during incidents ("who understands the token refresh path?" — silence), during extension ("can we add a second provider?" — nobody knows what assumptions are load-bearing), and during review of the next AI-generated change, which the assistant will happily build atop assumptions no human has examined. Debt compounding on debt.
It repays slowest under pressure. Reconstructing a theory from code alone is exactly the hard archaeology Naur described — and you'll be doing it at 2 a.m., with the pager going, which is the most expensive possible tuition.
"It works" is a checkpoint, not a finish line
So here's the definitional stake I want to plant. "It works" means the code passed the checks you thought to run. "It's done" means something bigger: someone on the team can explain it, extend it, and answer for it. Between those two lies everything that distinguishes engineering from artifact acceptance.
A definition of done for the assisted era, four questions:
- Can I narrate it? Walk the main path and the failure paths out loud, without reading line by line. If I can't, I'm accepting, not engineering.
- Do I know why it's shaped this way? What alternatives exist, and would I have chosen this one? Models produce a solution; engineering is knowing its neighbors.
- Do I know where it breaks? Expired tokens, empty lists, clock skew, the retry that retries a non-idempotent call. Generated code is famously fluent on the happy path and quietly improvisational off it.
- Would I defend it in review under my own name? Because that is, in fact, exactly what merging is.
None of this means "don't use the assistant." It means the human contribution has migrated. It used to live in typing; now it lives in specification, interrogation, and verification. The engineers thriving with these tools aren't the ones generating the most code. They're the ones who treat generation as the cheap step and comprehension as the deliverable — who read the diff like a reviewer, ask the model to explain its own choices, delete what they can't justify, and regenerate until the code matches a theory they actually hold.
Homework
- Run the narration test before your next assisted merge. Explain the change to a colleague — or a rubber duck — without reading the code aloud. Wherever you stall, you've found the debt. Pay it before merging, not after paging.
- Ask the assistant "why" three times. Why this library, why this error handling, why this structure. You're not testing the model; you're forcing the theory to form in your head.
- Sort your work into disposable and durable — explicitly. Vibe away on the disposable pile with a clear conscience. The durable pile gets the four questions, every time.
- Add one line to your PR template: "Can the author explain the failure modes of this change?" Watch what it does to merge behavior.
The tools will keep getting better, and the temptation to co-sign unread loans will grow accordingly. Naur's lesson stands: the program was never the product. The understanding was. Ship both.