Vibe coding began as an inside joke, coined by Andrej Karpathy to describe “fully giving in to the vibes, embracing exponentials, and forgetting that the code even exists.” There’s truth in the joke: we’ve all trusted autocomplete or an AI assistant a bit too much. But if you think vibe coding means skipping fundamentals and letting AI ship your app, you’re missing the point.
It’s funny because there’s truth to it. Every developer has, at some point, trusted autocomplete or an AI assistant a little too much and hoped for the best. But here’s the catch: if you think vibe coding means skipping the fundamentals and letting AI do the heavy lifting, you’re missing the point entirely.
The post below sums up the skeptic’s perspective — vibe coding feels cool in the moment, but once things break, the fantasy ends and the real developers step in. Debugging, deployment, and long-term maintenance bring you right back to square one — wallet out, hiring someone who actually knows the stack:

If you’re unwilling to learn how to develop software and rely on vibes to brute-force syntax, you’ll ship risk. This article clarifies what vibe coding is, where it shines, where it fails, and how to do it responsibly.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
In practice, vibe coding spans a spectrum: from devs leaning on AI-assisted autocomplete to non-developers prompting their way to scripts, prototypes, and small utilities. The promise is speed. The risk is shipping code you don’t understand.
To different people, vibe coding means different things. For some people, vibe coding is like autocomplete in their IDE, just prompting your way into a functional application. For others, it means non-developers writing code with the help of AI tools.
But vibe coding isn’t a silver bullet. Here’s a table, so you can see where it shines and where it crashes in a glance:
| Where Vibe Coding Works ✅ | Where It’ll Burn You ❌ |
|---|---|
| Personal Scripts – Python file organizer, batch renaming tool, download sorter. Worst case? You sort files manually. | Payment Systems – SQL injection vulnerabilities, plain text storage, insecure API endpoints. Lawsuit material. |
| Static Sites – Portfolio pages, landing pages, blog sites. Predictable patterns, minimal risk. | Real-time Features – WebSockets with race conditions, memory leaks, vanishing user data. |
| Learning Projects – Todo apps, weather widgets, calculators. Breaking these teaches you debugging. | Authentication Systems – Plain text passwords, no session management, JWT tokens in localStorage. |
| Prototypes & MVPs – Quick demos for clients, proof of concepts. You’re gonna rebuild anyway. | Healthcare/Finance Apps – HIPAA violations, PCI compliance failures, regulatory nightmares. |
| Data Visualization – Charts from CSV files, simple dashboards. D3.js + AI = quick pretty graphs. | Multi-tenant SaaS – Data isolation failures, cross-tenant data leaks, permission breakdowns. |
| CLI Tools – Markdown converters, file utilities, dev tools. Limited scope, easy to test. | Production APIs – No rate limiting, no caching strategy, no error handling. Server overload guaranteed. |
| Component Libraries – UI components, design systems. Reusable, testable, isolated pieces. | Database Migrations – Destructive operations, data loss, schema conflicts, orphaned records. |
| Content Sites – Blogs with markdown, documentation sites. Content-focused, minimal logic. | Real Money Gaming – Floating point errors, race conditions in transactions, incorrect payout calculations. |
In essence? Use vibes where the blast radius is small and failure is cheap. Avoid it where correctness, security, and compliance matter.
Here’s where the optimism collapses. When vibe coding turns into prompt-spamming or blind copy-pasting (especially among non-developers building full applications on pure vibes) it stops being creative and starts being reckless. These are the failure modes that turn a clever shortcut into a long-term liability.
Recent research from studies like the BaxBench experiment and Veracode analysis confirms that foundational AI models generate a significant percentage of insecure code, with one BaxBench study finding a minimum of 36%:
// What AI might generate when you ask for user authentication
const login = (username, password) => {
const user = database.query(`SELECT * FROM users WHERE username='${username}' AND password='${password}'`)
if (user) {
localStorage.setItem('user', JSON.stringify(user))
return true
}
}
// What just happened:
// 1. SQL injection vulnerability - someone can login with username: admin'--
// 2. Plain text passwords - your users' passwords are sitting naked in the database
// 3. Sensitive data in localStorage - any XSS attack now has full user access
The scary part? AI tools generate code like this all the time. They’ll happily create authentication systems that look functional but are security disasters waiting to happen. The AI doesn’t know your threat model, it just knows you asked for a login function that works.
Or take this real example – AI suggesting environment variables for API keys, then doing this:
// What AI might suggest for "secure" API calls
const apiKey = process.env.REACT_APP_API_KEY
fetch(`https://api.service.com/data?key=${apiKey}`)
.then(res => res.json())
.then(data => displayData(data))
// The problem: This is React frontend code
// During build, process.env.REACT_APP_API_KEY gets replaced with the actual value
// So in the browser, it becomes:
fetch(`https://api.service.com/data?key=sk-1234567890abcdef`)
// Now anyone can:
// 1. Open Network tab and see: api.service.com/data?key=sk-1234567890abcdef
// 2. View page source and find the hardcoded key in the bundle
// 3. Use that key for their own requests
The AI won’t always warn you – it just assumes you understand frontend vs backend context.
Vibe coding by blindly prompting your way into a product is like taking a loan from your future application. That puts scalability and maintenance at risk. Here’s what I mean:
// Month 1: Simple todo app
const todos = []
const addTodo = (text) => todos.push({text, done: false})
// Month 2: Added user support (vibed it)
const todos = {}
const addTodo = (userId, text) => {
if (!todos[userId]) todos[userId] = []
todos[userId].push({text, done: false})
}
// Month 3: Added categories (more vibing)
const todos = {}
const addTodo = (userId, categoryId, text, priority, tags) => {
if (!todos[userId]) todos[userId] = {}
if (!todos[userId][categoryId]) todos[userId][categoryId] = {items: [], metadata: {}}
// 50 more lines of nested conditionals...
}
// Month 4: "Why is everything so slow and why does adding a feature take 2 weeks?"
Each vibe coding session was built on top of the previous mess. Now you will have to pay actual developers to untangle what should’ve been a simple schema from the start, but you had no idea. “Prompt-drift” grows complexity without architecture: each “quick add” piles nested state and shape changes.
You can’t debug what you don’t understand. Concurrency, batching, and rate limits are common blind spots. Let’s look at the AI generated code below:
// AI generated this "optimized" data fetcher
const fetchUserData = async (ids) => {
return await Promise.all(
ids.map(id =>
fetch(`/api/user/${id}`)
.then(r => r.json())
.catch(() => null)
)
).then(results => results.filter(Boolean))
}
// aT first this hsiuld work fine... until production
// Problem: 100 users = 100 parallel API calls = server crashes
// Developer's debugging attempt: "AI, why is my server crashing?"
// AI's response: "Try adding a delay"
// New problem: Now it takes 100 seconds to load
The fix is simple if you understand the code, batch the requests, or implement proper rate limiting. But when you’re just vibing? You’re stuck in an endless loop of “AI, fix this” → “New problem” → “AI, fix this new problem.”
The worst part? These developers often end up in forums asking for help, but they can’t even explain what their code does because they never understood it in the first place.
I know AI tools PR has sold a lot of ideas to you, but to be sincere, you need to learn how to code to be a productive vibe coder, in whatever definition you fall into.
So let me say you only use autocomplete in your IDE, it makes you a faster developer, or I say you just prompt your way through an application, for redundant tasks or designs, you stand a better chance of fixing issues on your way to a functional application if you have a foundational experience, but for non-developers writing code with the help of AI tools, you are disadvantaged, and this is because developers stand a better chance of producing great scripts with AI.
No doubt AI tools generate incredible things, so much better than most code shipped to production, and there is a touch to results like this; they don’t come by just prompting most times. They come by precision with these LLMs.
What you must be to become a vibe coder is a major tip to effectively vibe code, the remaining tips in this section supplement it.
Break work into testable, composable units.
❌ Bad approach:
"Build me a complete social media app with users, posts, and comments"
✅ Good approach:
1. "Create a user authentication system with JWT and bcrypt password hashing" 2. "Build a post creation component with image upload and validation" 3. "Add a comment system with nested replies and moderation"
This gives you manageable pieces you can actually understand and debug.
Stick to popular, well-documented technologies. Here’s what works best with AI:
// AI generates higher quality code for popular stacks
const goodStack = {
frontend: ['React', 'TypeScript', 'Tailwind CSS'],
backend: ['Node.js', 'Express', 'Prisma'],
database: ['PostgreSQL', 'Redis']
}
// Versus obscure frameworks where AI struggles
const problematicStack = ['Svelte Kit', 'Deno Fresh', 'SurrealDB']
If you don’t build your reusable components earlier on, you’ll get different-looking components for the same thing, like in the example below:
// AI generates this in one file
<button className="px-4 py-2 bg-blue-500 text-white rounded">
Login
</button>
// Then this in another file
<button className="padding: 8px 16px; background: #3b82f6; color: white; border-radius: 4px">
Submit
</button>
// And this in a third file
<button style={{backgroundColor: 'blue', padding: '10px', color: 'white'}}>
Save
</button>
So make sure to build your components earlier:
// Define once, use everywhere
const Button = ({ variant = 'primary', children, ...props }) => {
const baseClasses = "px-4 py-2 rounded font-medium transition-colors"
const variants = {
primary: "bg-blue-500 hover:bg-blue-600 text-white",
secondary: "bg-gray-200 hover:bg-gray-300 text-gray-800"
}
return (
<button
className={`${baseClasses} ${variants[variant]}`}
{...props}
>
{children}
</button>
)
}
So that you can ask your AI Agent to reuse these components elsewhere. Here’s to consistency.
Try to be as specific as possible, not vague. For example;
❌ Bad prompt:
"Make a login form"
✅ Good prompt:
"Create a React login form with: - Email validation (proper email format) - Password field with show/hide toggle - Remember me checkbox - Loading state during submission - Error handling for network failures - Accessibility attributes for screen readers - Form validation using Formik and Yup"
Secondly, put in enough thought into the context. Always provide context like this:
Current tech stack: Next.js 14, TypeScript, Tailwind, Prisma Database: PostgreSQL with User and Post models Authentication: NextAuth.js with JWT I need to create a post creation form that: [your requirements here] Here's my current User model: [paste relevant code]
Lastly, learn to iterate, and not just generate:
// Ask for basic structure first
interface Post {
id: string
title: string
content: string
authorId: string
createdAt: Date
}
const createPost = async (data: Omit<Post, 'id' | 'createdAt'>) => {
// Basic implementation
}
// Then iterate with improvements
interface Post {
id: string
title: string
content: string
authorId: string
tags: string[] // Added
imageUrl?: string // Added
isPublished: boolean // Added
createdAt: Date
updatedAt: Date // Added
}
You must always look out for safety and quality as a developer. These are tips for code quality and safety:
Before any AI generation, make sure you:
git add . git commit -m "Working auth system before adding post features"
And after AI generates code:
# After AI generates code git add . git commit -m "AI: Added post creation with image upload"
So when something breaks, which it should, you can :
git reset --hard HEAD~1 # Back to safety
Some AI models love to skip validation. Always add it:
// AI might generate this basic form
const createUser = (userData) => {
return database.user.create({ data: userData })
}
// You should add validation like this
import { z } from 'zod'
const userSchema = z.object({
email: z.string().email('Invalid email format'),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, 'Password must contain uppercase, lowercase, and numbers'),
name: z.string().min(2, 'Name too short').max(50, 'Name too long')
})
const createUser = async (userData) => {
const validatedData = userSchema.parse(userData)
return database.user.create({ data: validatedData })
}
AI tools handle the happy paths, and they only test edge cases when you ask them to help debug. So it’s your job to do this from the beginning, or ask that it be done from the start as well:
// AI generates the happy path
const calculateTotal = (items) => {
return items.reduce((sum, item) => sum + item.price, 0)
}
// You need to handle edge cases
const calculateTotal = (items) => {
if (!Array.isArray(items)) {
throw new Error('Items must be an array')
}
if (items.length === 0) {
return 0
}
return items.reduce((sum, item) => {
if (typeof item.price !== 'number' || item.price < 0) {
throw new Error(`Invalid price: ${item.price}`)
}
return sum + item.price
}, 0)
}
When vibe coding, you must document all AI-generated code, so that you can easily spot where problems come from. Here’s an example of how you’d do it:
/**
* AI-Generated: Post creation with image upload
* Generated: 2024-01-15
* Modifications: Added error handling and validation
* Known limitations: Only supports JPEG/PNG, max 5MB
* TODO: Add support for video uploads
*/
const createPostWithImage = async (postData: PostData) => {
// AI-generated core logic...
// Human-added validation
if (postData.image && postData.image.size > 5_000_000) {
throw new Error('Image too large')
}
// Rest of implementation...
}
Sometimes when vibe coding, AI can get confused. Here are some warning signs:
// AI starts generating increasingly broken code
const confusedAI = {
attempt1: "const user = getUserData()",
attempt2: "const user = await getUserData().then(data => data.user.userData)",
attempt3: "const user = Promise.resolve(getUserData()).then(async (data) => await data?.user?.userData?.result)"
}
When this happens, take a step back to reset with clear context:
I'm starting fresh. Here's what I need: Current working code: [paste your working authentication system] New requirement: Add password reset functionality with email verification Tech stack: [specify again] Database schema: [show relevant models]
Tools like Cursor and Windsurf have rules, and those rules should look like this for better results:
// .cursor/rules/security.md
{
"rules": [
"Always use environment variables for secrets",
"Use parameterized queries for database operations",
"Implement input validation with Zod schemas",
"Add rate limiting to API endpoints"
]
}
If the AI-generated code is way above your skill level, turn it down a notch. You should understand at least 80% of what gets generated.
const complexAIGenerated = async <T extends Record<string, unknown>>(
data: T,
transform: <K extends keyof T>(key: K, value: T[K]) => Promise<T[K]>
): Promise<Partial<T>> => {
return Object.fromEntries(
await Promise.all(
Object.entries(data).map(async ([key, value]) => [
key,
await transform(key as keyof T, value)
])
)
) as Partial<T>
}
Ask for a simpler version:
// Much better - you can understand and modify this
const transformUserData = async (userData) => {
const result = {}
for (const [key, value] of Object.entries(userData)) {
if (key === 'password') {
result[key] = await hashPassword(value)
} else if (key === 'email') {
result[key] = value.toLowerCase().trim()
} else {
result[key] = value
}
}
return result
}
Vibe coding should make you a faster developer, not a dependent one. Use AI to handle the tedious stuff so you can focus on generic logic.
Those who roll their eyes at vibe coding might forget they’ve been doing the same thing for years — only slower. Whether it’s relying on a framework scaffold, an autocomplete, or a dozen helper libraries, developers have always used tools to smooth the rough edges. AI just makes that habit more visible.
Let’s be real about what ‘traditional’ coding actually looks like in 2025:
The only real difference is the middleman: one developer Googles for answers, the other asks an AI. Both rely on external knowledge to move faster. Vibe coders aren’t lazy, they’re just optimizing for effort and offloading the repetitive parts so they can focus on the creative ones.
Remember the left-pad package? It added a few spaces to the left of a string and still racked up over a million downloads. Here’s what it did:
function leftPad(str, len, ch) {
str = String(str);
var i = -1;
if (!ch && ch !== 0) ch = ' ';
len = len - str.length;
while (++i < len) {
str = ch + str;
}
return str;
}
For something this simple, developers were more than happy to install a package instead of writing five lines of code. That’s not laziness — it’s pragmatism.
Yet the same people who used left-pad will often roll their eyes at developers using AI tools to do exactly the same thing, just faster.
The difference is control. With packages like left-pad, you depended on someone else’s codebase (and downtime, as NPM learned the hard way). With vibe coding, you can generate the same utility in seconds, fully editable and tailored to your project.
That’s the real power here: vibe coding is about reclaiming effort from grunt work. It lets you offload trivial, repetitive work so you can focus on what actually matters. If there’s a small, fixable pain point in your workflow that’s not worth a weekend of boilerplate, that’s exactly where AI belongs.
Instead of dismissing vibe coding altogether, focus on using it intentionally. The key isn’t avoiding AI, it’s knowing when it actually saves you time
Not all vibe coding is reckless. Context matters. Used thoughtfully, it’s a legitimate way to accelerate simple builds or experiment quickly. Used carelessly, it’s a shortcut to unmaintainable code and a security risk. Here’s a quick breakdown of where vibe coding can boost your productivity, and where you should absolutely steer clear:
| Good for Vibe Coding | Stay Away From |
|---|---|
| Personal scripts & automation | Payment processing |
| Static portfolio sites | User authentication |
| Learning projects | Healthcare apps |
| Prototypes & demos | Real-time features |
| Component libraries | Production APIs |
If you plan to ship production software, don’t rely on vibes alone. Learn the language, understand the architecture, and use AI to accelerate — not replace — your reasoning. Vibe code responsibly: keep blast radius small, validate inputs, test edge cases, and document what the AI generated.
What’s your biggest vibe coding challenge? Share your experience — what worked, what didn’t, and where AI helped or hurt.

GitHub SpecKit brings structure to AI-assisted coding with a spec-driven workflow. Learn how to build a consistent, React-based project guided by clear specs and plans.

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more – writing cleaner, more dynamic CSS with less JavaScript.

Kombai AI converts Figma designs into clean, responsive frontend code. It helps developers build production-ready UIs faster while keeping design accuracy and code quality intact.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 22nd issue.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now