What Is Vibe Coding? The Honest Definition
Vibe coding means accepting AI-generated code without reading it. That is a real technique with a narrow safe range — here is where it works, where it fails, and how to review what it produces.

"Vibe coding" is used to mean two very different things, and the difference matters more than the term does.
The original meaning is specific and slightly reckless: you describe what you want, you accept the code the model produces without reading it, and when something is wrong you describe the problem rather than fixing it yourself. The defining property is not that an AI wrote the code. It is that no human read it. The phrase was popularized by Andrej Karpathy in early 2025, and it was half a joke — the idea being to lean into the flow and stop treating the code as something you personally maintain.
The second meaning is what most people actually do: AI-assisted development, where a model drafts and a human reviews, edits, and takes responsibility. That is not vibe coding. That is programming with a very fast, very confident junior colleague.
Conflating the two produces bad advice in both directions — people condemning a legitimate prototyping technique, and people shipping unreviewed code to production because a blog post told them it was the new normal. So it is worth separating them properly.
Where vibe coding genuinely works#
Real vibe coding has a narrow, real safe range, and the boundary is blast radius rather than difficulty.
It works well for throwaway artifacts: a script that renames three hundred files, a one-off data transformation, a chart for a meeting tomorrow. The cost of a bug is that you notice and re-run it.
It works well for prototypes whose purpose is to be discarded. If you are trying to find out whether an interaction feels right, the code is a question, not an answer. Reviewing it carefully is wasted effort on something you intend to delete.
It works well for unfamiliar territory where the alternative is nothing. A shell pipeline in a syntax you use twice a year, a first pass at an API you have never touched. You are not going to write better code from memory, and the generated version at least gives you something to react to.
It works badly everywhere else, and the failure is not usually "the code does not run." It is "the code runs, produces plausible output, and is wrong in a way that surfaces later." That is the expensive failure mode, and it is the one unreviewed code specializes in.
Where it fails, specifically#
Five categories, in rough order of how often they bite:
- Boundary conditions. Empty input, a single element, the last index, the maximum value. Generated code is fluent about ranges and routinely off by one at the edge.
- Constraints stated outside the prompt. The model optimizes what you described. If you did not mention that n reaches a million, you will get something readable and quadratic.
- State and concurrency. Anything involving shared mutable state, retries, ordering, or partial failure. These bugs do not appear in a test run; they appear under load.
- Security-adjacent code. Authentication, authorization, input validation, anything constructing a query from user data. Generated code often reproduces the shape of a correct solution with the guard missing.
- Silent architectural drift. Each request is answered locally and reasonably. Twenty requests later, three modules do the same thing three ways and nothing enforces an invariant.
A concrete example of a plausible wrong answer#
Ask for pagination on a list endpoint and you will frequently get something like this:
// GET /users?page=2&limit=20
app.get("/users", async (req, res) => {
const page = req.query.page || 1;
const limit = req.query.limit || 20;
const users = await User.find({})
.skip((page - 1) * limit)
.limit(limit);
res.json(users);
});It runs. It returns the right rows for ?page=2&limit=20. It is also wrong in four ways that a passing glance will not catch:
req.query.pageis a string.(page - 1) * limitcoerces it, so it happens to work — butpageof"2"with a defaultlimitgivesskip("2" - 1 * 20), and any code path that concatenates instead of subtracting silently breaks. Parse explicitly.limitis unbounded.?limit=1000000is now a denial-of-service vector against your own database.- Negative and non-numeric input is unguarded.
?page=-5produces a negative skip, which different drivers handle differently and none of them handle well. - There is no total count and no stable sort, so a caller cannot know when to stop and rows shift between pages as data changes.
The corrected version is not cleverer, just specified:
const MAX_LIMIT = 100;
app.get("/users", async (req, res) => {
// Clamp before use: an unbounded `limit` from the query string is a
// denial-of-service vector, and NaN silently becomes a negative skip.
const page = Math.max(1, Number.parseInt(req.query.page, 10) || 1);
const limit = Math.min(
MAX_LIMIT,
Math.max(1, Number.parseInt(req.query.limit, 10) || 20),
);
// Sort by a stable, unique key — without it, rows move between pages
// whenever the underlying data changes mid-pagination.
const [users, total] = await Promise.all([
User.find({}).sort({ _id: 1 }).skip((page - 1) * limit).limit(limit),
User.countDocuments({}),
]);
res.json({ users, page, limit, total });
});Nothing here required deep expertise. It required reading the code as a reviewer, which takes about forty seconds and is the entire difference between the two versions.
The review pass that catches most of it#
Three checks, in this order, on any generated function:
- Boundaries. What happens on empty, on one element, on the maximum, on the negative case? Read the loop bounds specifically.
- Structure against constraint. Does the data structure match the size and access pattern you actually have? A list where you needed a set, a scan where you needed an index.
- Claim against code. If the explanation says linear, find the line that makes it linear. If there is a sort in there, the explanation is wrong.
If it touches auth, money, or user data, add a fourth: name the input that a hostile user controls, and follow it through every line.
Prompting is specification, not conversation#
The largest quality gain does not come from clever phrasing. It comes from stating the things you know and the model does not: the size of the input, the invariants, the failure behavior, the constraints of the surrounding system.
Compare "write a function to find duplicates in a list" with "given up to 10⁶ 64-bit integers that may be negative, return each value appearing more than once, in first-appearance order, in a single pass; the input cannot be modified." The second produces a materially different and usually correct answer, because you did the specification work the first prompt left to chance.
The useful habit is to write those constraints down before prompting. You will find that half the time, writing the specification tells you the answer.
Why this shows up in interviews#
Hiring is adjusting to all of this, in both directions at once. Some companies now permit an AI assistant in specific rounds and set problems where producing code is not the hard part — the grading moves to specification, verification, and debugging, which are exactly the skills above. Others have reinstated in-person or proctored rounds specifically to remove assistance from the equation.
Either way, the thing being measured has shifted toward judgment. "The assistant suggested a hash map here, but the keys are unbounded strings from user input, so I would use a bounded structure" is now a hire signal in a way that reciting a template no longer is.
Practice accordingly. Predict what will be wrong before you read the output. Rewrite a generated solution to see if you can. Explain a piece of code you did not write, out loud, without the explanation in front of you.
For the live interview itself, Stealth Interview is a desktop app for macOS and Windows that reads the coding problem from a screenshot and returns a working solution with a step-by-step explanation and its time and space complexity, transcribes the interviewer's audio in real time, and stays invisible to screen sharing. It runs on multiple AI models with keyboard-shortcut control. The explanation is the part worth reading — being handed an approach you can defend is a different position from being handed an answer you cannot.
The honest summary#
Vibe coding, in the strict sense, is a legitimate technique with a narrow safe range: use it where the cost of being wrong is that you notice. Outside that range, what you want is AI-assisted development with a real review pass, and the review pass is short.
The skill that is actually appreciating in value is not prompting. It is the ability to look at confident, plausible, well-formatted code and tell, quickly, whether it is correct. That skill is built by reading code critically and by writing enough of it yourself that you know what the failure modes feel like — which is the same thing it has always been built by.
Frequently asked questions
- What does vibe coding actually mean?
- In its original sense it means describing what you want, accepting the code the model produces without reading it closely, and iterating by describing the next thing rather than editing the code yourself. The defining property is not that AI wrote the code — it is that no human read it. Most work people label vibe coding is really AI-assisted development, where the output is reviewed.
- Is vibe coding bad practice?
- It depends entirely on the blast radius. For a prototype, a one-off script, or a personal tool where the worst outcome is that it does not work, it is an efficient use of time. For code that touches authentication, money, personal data, or anything with a migration path, shipping code nobody read is how incidents happen.
- Does vibe coding make you a worse engineer?
- It can, through a specific mechanism: reading a correct solution produces recognition, not recall. If you only ever accept generated code you will keep the ability to recognize good code and slowly lose the ability to produce it. The fix is cheap — periodically implement something from an empty file, and always review generated code as a reviewer rather than a reader.
- How do I review AI-generated code quickly?
- Check three things in order: the boundary conditions, the data structure choice against the stated constraint, and the complexity claim against the actual code. Those three catch the large majority of plausible-looking generated bugs, and the pass takes under a minute on a short function.
