Vibe Coding: Do You Actually Know What You Are Doing?
In February 2025, Andrej Karpathy posted this on X: “There’s a new kind of coding I call ‘vibe coding’, where you fully give in to the vibes, embrace exponentials, and forget that the code even exists. […] I’m building a project or webapp, but it’s not really coding - I see stuff, I say stuff, stuff happens, I’m vibing.”
He was describing something real. Most developers who use AI tools daily recognize the mode: you type a rough description, the model produces code, you glance at it, it looks about right, you accept it and move on. It works. You ship faster. The vibes are good.
The problem isn’t the mode itself. The problem is when you stop noticing which mode you’re in.
What Vibe Coding Actually Is
Vibe coding is accepting code you haven’t understood. Not “read quickly and understood enough” - accepted without meaningful engagement. You ran it, it seemed to work, and you moved on.
This isn’t always wrong. Boilerplate doesn’t need deep understanding. A Dockerfile template, a standard Prettier config, a basic GitHub Actions workflow - these have low leverage on system correctness. Accepting them at face value is fine.
The problem is that vibe coding doesn’t stay limited to boilerplate. The same pattern - see, accept, move on - bleeds into places where understanding matters: authentication logic, data migrations, concurrency code, error handling, business rules.
The Understanding Gradient
Not all code demands the same level of understanding. There’s a rough gradient:
High stakes, understand deeply: anything that runs in production at volume, anything that touches money or user data, anything that handles failures, anything in the core domain of your application.
Medium stakes, understand the interface: library integrations, infrastructure config, test setup. You should understand what it does and when it fails, even if you don’t understand every line.
Low stakes, vibe freely: scaffolding, one-off scripts, dev tooling, anything you could delete and rewrite in an afternoon.
Vibe coding is appropriate at the low end. The error is applying the same mental posture to the high end because AI makes it feel just as easy.
How You Know You’ve Crossed the Line
The diagnostic question: can you explain this code to someone else?
Not “can you describe what it does” - you can usually do that from reading it. Can you explain why it’s correct? Can you articulate what happens in the error case? Can you describe what assumptions it makes about its inputs?
If the answer is no, you’re holding a fragment of code you don’t own. You can’t debug it when it fails. You can’t modify it safely. You can’t review a PR that touches it. You’ve outsourced not just the writing but the understanding, and you have a gap in your codebase where your comprehension should be.
This accumulates. A few vibe-coded modules become a large part of the codebase that the team navigates carefully, not understanding why things are wired the way they are, afraid to touch it. The codebase becomes archaeology.
The Specific Failure Modes
Silent incorrectness: AI-generated code often fails by producing something that is syntactically correct and superficially reasonable but wrong in a subtle way. It handles the happy path. It misses the edge case that you would have caught if you’d written it yourself and thought through the logic. Because you vibed through the review, you don’t catch it either.
Context blindness: the model doesn’t know your system. It doesn’t know that you have a soft delete pattern, that this table has a different timestamp format, that this API has a known bug you’re working around. The code it generates is correct for the generic version of your problem, not the specific version in your codebase. If you don’t bring that context in review, it doesn’t get there.
Inconsistent patterns: across a codebase where different sections were vibe-coded at different times, you end up with inconsistent patterns for the same thing - three different ways of handling errors, two different approaches to pagination, multiple authentication patterns. No one decided on these. They accumulated.
Invisible debt: debt you created intentionally can be tracked and addressed. Debt you accumulated by not understanding what you built is invisible. You don’t know it’s there until you’re debugging something unrelated at 11pm and discover that the code you accepted six months ago makes a wrong assumption about the database state.
What to Do Instead
The goal isn’t to reject AI assistance or to read every generated line with the same intensity. The goal is to stay in the driver’s seat.
Write the specification before you prompt. If you can write a clear description of what the function should do, what it should accept, what it should return, what it should do on error - you understand the problem. The model’s output is an implementation of your spec. Review it as an implementation, not as a proposal.
Break the flow deliberately on complex code. When you accept a non-trivial piece of generated code, force yourself to articulate what it’s doing before moving on. Not in writing, not as a formal review - just a five-second internal check: do I understand this? If the answer is no, that’s a signal to read more carefully.
Own the architecture even if you don’t write every function. The decisions about how the system is structured, where state lives, how components communicate - make these yourself. If you accept a structural decision from a model without thinking it through, you’re vibing at the wrong level of abstraction.
Use AI for the low-leverage parts. The more comfortable you get with delegating boilerplate, the more bandwidth you have for genuinely understanding the parts that matter. Vibe coding is a useful mode. The problem is scope creep.
How Understanding Is Lost
The loss of understanding from vibe coding is gradual. It’s not a single decision to stop reading code. It’s a series of small acceptances that individually seem reasonable.
You accept a function because it passed the tests. You accept a module because it worked in the demo. You accept an architectural decision from the model because it seemed plausible and you were moving fast. None of these is catastrophic on its own. The problem is that they accumulate, and the accumulation is invisible.
Six months later, a junior developer asks why the authentication works the way it does. You explain it, and partway through you realize you’re describing what you think it does rather than what you know it does. Or a bug appears in a module you remember writing but don’t remember understanding - and debugging it feels like reading someone else’s code.
This is the real cost of vibe coding at the wrong level: not the immediate bugs, but the progressive loss of the mental model you need to work confidently in your own codebase.
Understanding is recoverable. You can read the code, trace the logic, rebuild the mental model. But it takes time, and it’s time you wouldn’t have needed to spend if you’d engaged with the code when it was first generated.
Vibe coding is a mode, not an identity. The question is not whether you ever use it - of course you do, on scaffolding and boilerplate and throwaway scripts. The question is whether you notice when you’ve drifted into it on code that deserves more than a glance.