The term “vibe coding” started as a joke and became a workflow.
Andrej Karpathy described it well: you give the AI a loose description, accept whatever it produces, and keep iterating until the result feels right. You do not read every line. You do not trace the logic. You just… vibe with it.
For a weekend prototype, that works. For a production system that processes payments, stores user data, or runs at scale, it does not. The question is not whether vibe coding is valid. It is where the boundary sits between letting the AI drive and keeping your hands on the wheel.
What vibe coding actually looks like
Vibe coding has a recognisable pattern. The prompt is loose:
Build me a dashboard that shows user signups over time with a chart.
The tool generates a full page — data fetching, chart library, layout, styling. You look at the browser. The chart renders. The data seems right. You move on.
What you skipped:
- Whether the data query is efficient or will time out at scale
- Whether the chart library adds 200kb to your bundle
- Whether the date grouping handles timezones correctly
- Whether the API endpoint is authenticated
- Whether the data includes deleted users
- Whether the error state shows anything useful
None of those are nitpicks. They are the kinds of problems that show up a month later when the dashboard loads slowly, the numbers look wrong for users in different timezones, or someone discovers the endpoint is publicly accessible.
Vibe coding works when those problems do not matter. It does not work when they do.
What supervised coding looks like
Supervised coding uses the same AI tools with a different posture. The developer stays in control of the decisions. The tool handles the mechanical work.
Same dashboard, supervised approach:
Step 1 — Data layer:
Write a query that returns daily signup counts for the last 30 days.
Group by UTC date. Exclude soft-deleted users.
Return the result as JSON from an authenticated API route.
Do not add a chart library yet.
You verify the query. Check the output shape. Confirm authentication works. Then move to the next layer:
Step 2 — Chart:
Add a line chart using the lightweight chart library already in this project.
If there is no chart library, use Chart.js.
Render the data from the API route created in step 1.
Handle the loading and error states.
You review the implementation. Check the bundle size. Verify the error state. Then:
Step 3 — Layout:
Place the chart in the existing admin dashboard layout.
Follow the card component pattern used on other dashboard pages.
Do not restyle existing components.
The result might look identical to the vibe-coded version. The difference is that you verified each layer before stacking the next one on top. When something breaks, you know which layer to look at.
The spectrum, not the binary
Most real development sits between these two extremes. The useful question is not “which mode am I in?” but “does this task tolerate the risk of not reading the code carefully?”
Low-risk tasks where vibe coding is fine:
- Throwaway prototypes and proof-of-concept demos
- Internal scripts that run once
- Learning exercises where you are exploring a new library
- Hackathon projects where speed is the only metric
- Personal side projects with no users
High-risk tasks where supervised coding is necessary:
- Anything that handles user data or authentication
- Code that will run in production for months
- Features that interact with payments, permissions, or external APIs
- Changes to shared libraries or core abstractions
- Code that other developers will maintain
The boundary is not about the difficulty of the code. Simple code in a critical path needs supervision. Complex code in a throwaway script does not.
The three failure modes of vibe coding
When vibe coding goes wrong, it tends to fail in one of three ways.
The “it works but I do not know why” problem
The tool generates code that passes your manual test. You ship it. A week later, a bug appears in an edge case. You open the file and realise you do not understand the implementation well enough to debug it.
This is the most common failure mode. The code is not bad. Your relationship to the code is bad. You are maintaining something you did not write and did not read carefully. It is the same problem as inheriting a codebase from a developer who left, except the developer was an AI and it left thirty seconds after writing it.
The fix is not reading every line — that defeats the point of using a tool. The fix is understanding the approach well enough to debug it. If you cannot explain the strategy in one sentence, you do not understand it well enough to maintain it.
The “accumulated drift” problem
You vibe-code feature one. It works. You vibe-code feature two on top of it. That works too. By feature five, the codebase has five different error handling patterns, three ways of fetching data, two competing state management approaches, and no consistent file organisation.
Each feature was generated in a separate session. The tool did not carry context between sessions. It solved each problem locally without considering the whole.
This is the problem that supervised coding prevents most effectively. When you review each layer, you catch inconsistencies before they compound. When you specify constraints like “follow the existing pattern in this file,” the tool produces code that fits the codebase, not just the prompt.
The “false confidence” problem
AI-generated code looks authoritative. It does not contain hedging comments like // not sure if this handles the timezone edge case. It does not leave TODO markers on uncertain logic. It writes clean, confident code that may be quietly wrong.
The confidence is aesthetic, not epistemic. The tool produced the most likely next tokens, not the provably correct implementation. When you skip review, you inherit that false confidence.
A practical example:
// AI-generated date comparison
const isExpired = new Date(expiryDate) < new Date();
This looks correct. It is correct — if expiryDate is always a valid ISO string and both dates are in the same timezone context. In a browser with users across timezones and an expiryDate that comes from a database without timezone information, this comparison can be wrong by up to a day.
The tool did not flag the assumption because it does not know the assumption exists. That is the reviewer’s job.
How I decide which mode to use
I do not have a formal framework. I have a one-question test:
If this code has a subtle bug, how expensive is it to find and fix?
If the answer is “I will notice immediately and fix it in five minutes,” vibe coding is fine. If the answer is “it might corrupt data for hours before anyone notices,” supervised coding is the only responsible choice.
For my portfolio site, building a new component layout or a content page is low cost. If the CSS is slightly off, I see it and fix it. For a Laravel API endpoint that processes webhook data, every line matters. A missed validation rule or a wrong status code can cause silent failures downstream.
The same developer can use both modes in the same day. The skill is knowing when to switch.
Making supervised coding faster
Supervised coding does not have to be slow. The point is not to type every line manually. The point is to verify every decision intentionally.
These habits make supervised coding nearly as fast as vibe coding while keeping the safety:
Prompt in layers. Ask for the data model first, then the business logic, then the presentation. Review each layer before requesting the next. This is faster than reviewing a 500-line diff because each review is small and focused.
Give constraints upfront. The more specific the prompt, the less cleanup the review needs.
Bad: Build a user settings page.
Good: Add a settings page using the existing form component pattern.
Only include email and notification preferences.
Use the same validation approach as the profile edit page.
Do not add new dependencies.
Use the tool to verify its own output. After the tool generates code, ask it to review what it wrote:
Review the code you just wrote.
Check for missing error handling, uncovered edge cases,
and inconsistencies with the existing codebase.
This catches a surprising number of issues. The tool is better at reviewing than generating because review is a narrower task.
Run tests after every change, not just at the end. If the tool edits three files, run the suite after each edit. Catching a regression immediately is cheaper than debugging it after five more changes.
The practical version
Vibe coding is useful for throwaway work where speed matters and correctness is cheap to verify. Supervised coding is necessary for production work where bugs are expensive and the code will be maintained by humans.
The mistake most developers make is not choosing vibe coding. It is vibe coding production features because the tool made it feel easy. Easy generation does not mean easy maintenance. Easy tests passing does not mean correct behavior. Easy-looking code does not mean understood code.
Use the AI to move fast. Use your judgment to decide how fast is safe.