In early 2025, Andrej Karpathy posted a tweet that gave a name to something a lot of us had already been doing. He called it "vibe coding." The idea: you fully give in to the vibes, embrace exponentials, and forget that the code even exists. You describe what you want, the AI writes it, you run it, and if it doesn't work, you paste the error back in and try again. You don't really read the code. You just keep going until it works.
I'll be honest. It's fun. There's a specific kind of dopamine hit when you describe something in plain English and watch working code appear in seconds. The first time I vibe-coded a small tool from scratch without writing a single line myself, I genuinely thought: this changes everything.
And in some ways, it does. But not in the way a lot of people seem to think.
What Vibe Coding Gets Right
I don't want to be the person who dismisses something useful because it doesn't fit my idea of how software should be written. Vibe coding is genuinely good for certain things.
Prototypes and throwaway scripts. Last month I needed a quick Python script to parse a messy CSV export from a vendor, reformat the dates, and dump it into a Firestore collection. The whole thing was maybe 80 lines. I described what I wanted, Claude wrote it, I ran it, it worked. Total time: about four minutes. I didn't need that code to be maintainable. I needed it to run once and be correct. Vibe coding was perfect for that.
Lowering the barrier to entry. Non-programmers can now build things that actually work. A product manager who wants to prototype a dashboard. A designer who wants to test an interaction idea. A student who wants to see if their concept is feasible. These are real use cases, and they matter.
Boilerplate you already understand. When I'm setting up a new Spring Boot controller with standard CRUD endpoints, I don't need to think hard about the structure. I've written hundreds of them. Having AI generate the skeleton saves me typing without any loss of understanding. I know exactly what every annotation does. I know what the service layer should look like. The AI is just a faster keyboard.
So yes, vibe coding has real value. The problem starts when people treat it as a substitute for engineering.
Where It Falls Apart
You can't debug what you don't understand
A few weeks ago, a junior developer on a project I was reviewing had used AI to generate a date-range filtering function for a reporting feature. It looked clean. It passed the basic tests. But it had a subtle off-by-one error in how it handled timezone boundaries. If you queried for "all orders on March 15th" and your server was in UTC but your users were in UTC+3, you'd miss orders placed between 9 PM and midnight local time.
The developer couldn't find the bug because they hadn't written the code. They didn't know why the function converted timezones the way it did. They didn't know what ZonedDateTime.truncatedTo(ChronoUnit.DAYS) actually does under the hood. So when the bug report came in, they spent three hours staring at code that looked right, because they didn't have the mental model to see why it wasn't.
Write the code yourself and you'll at least have a theory about where things could go wrong. When AI wrote it, you're starting from zero.
Error handling vanishes
This one drives me up the wall. AI-generated code almost always handles the happy path beautifully. The sunny-day scenario where everything works. But production isn't sunny. Networks time out. Databases return null where you expected a row. Users submit forms with emoji in the phone number field.
I reviewed a pull request last month where an AI-generated Android repository class made three Retrofit API calls in sequence. No try-catch blocks. No timeout configuration. No retry logic. No handling for what happens when call two fails but call one already committed a state change. Just three clean suspend fun calls in a row, as if the network is a reliable, deterministic thing.
The code compiled. It ran fine on fast WiFi. It would have crashed in production within hours.
Security is invisible
This is the one that actually worries me. Security vulnerabilities don't look like bugs. Code runs. Tests pass. Everything works. You just happen to have an endpoint that doesn't validate authentication tokens properly, or a query that concatenates user input directly into a SQL string, or a file upload handler that doesn't check MIME types.
If you're vibe coding and not reading the output carefully, you won't catch these. They don't throw exceptions. They don't fail tests. They sit there quietly until someone exploits them.
I've seen AI-generated code that stored passwords with MD5 hashing. It worked. Tests passed. Just wasn't secure by any modern standard. The person who accepted that output didn't know enough to question it.
"It works when I test it manually once"
Vibe coding produces code that passes the simplest possible validation: you run it, you check the output, it looks right. But that's not testing. That's a spot check.
What about concurrent access? What about inputs at the boundary of valid ranges? What about the behavior when the database has ten million rows instead of ten? What about memory usage over time? What happens when you call this function a thousand times in a tight loop?
These questions don't come naturally when you didn't write the code. You don't have the context to know where the weak spots are.
The "It Works" Trap
There's a specific failure mode I keep seeing, and I think it's the core problem with vibe coding as a methodology. I call it the "it works" trap.
Someone prompts an AI. The AI generates code. They run it. It produces the expected output. They ship it.
But "it works" is the lowest bar in software development. Code that works is just the starting point. The real questions are:
- Does it work with unexpected input?
- Does it work under load?
- Does it work when dependencies fail?
- Will it still work after the next library update?
- Can someone else read it and understand what it does?
- Does it handle the edge case you haven't thought of yet?
Here's a concrete example. Say you ask AI to write a SwiftUI view that displays a paginated list of items fetched from an API. The AI gives you something that works. You scroll, new items load, everything looks great. But does it handle the case where the API returns an empty page in the middle of the results? Does it deduplicate items if the underlying data changes between page fetches? Does it cancel in-flight requests when the user leaves the screen? Does it handle the user pulling to refresh while a page load is already in progress?
These aren't theoretical concerns. These are the things that cause bugs in production. And if you didn't think through the implementation yourself, you probably won't think to test for them either.
What Engineering Actually Looks Like
I'm not trying to gatekeep here. I don't think you need a CS degree to be a software engineer. I don't think there's one right way to build software. But I do think there's a real difference between coding and engineering, and it's worth being specific about what that difference is.
Understanding trade-offs. Every technical decision is a trade-off. Using an in-memory cache makes reads faster but introduces stale data risk. Using a relational database gives you ACID guarantees but limits horizontal scaling options. Choosing Server-Sent Events over WebSockets is simpler but only allows one-directional communication. Engineering means understanding these trade-offs and making intentional choices. Vibe coding means accepting whatever the AI picks.
Designing for failure. Good engineers spend a lot of time thinking about what goes wrong. What happens when the payment provider is down? What happens when a user submits the same form twice? What happens when a background job takes longer than its timeout? The difference between a prototype and a production system is almost entirely in how failure is handled. And AI-generated code almost never handles failure well, because the prompts almost never describe failure scenarios.
Thinking about the reader. Code gets written once and read dozens of times. Variable names matter. Function boundaries matter. The decision to extract a helper or leave logic inline matters. When I write code, I'm thinking about the person who reads it six months from now. Often that person is me, and I've already forgotten the context. Vibe-coded output is often technically correct but not organized for human comprehension, because the AI doesn't know your team's conventions or what context future readers will need.
Knowing when NOT to build something. This is maybe the most underrated engineering skill. Sometimes the right answer is: don't build that feature. Use an existing library. Simplify the requirements. Push back on the spec. An AI will always try to build what you ask for. It won't tell you the request is a bad idea.
The boring stuff. Monitoring. Alerting. Log aggregation. Database migrations. Graceful degradation. Circuit breakers. Retry policies with exponential backoff. Nobody writes blog posts about these things because they're not exciting. But they're what keeps production running at 3 AM when you're asleep. Vibe coding doesn't produce any of this.
The Middle Ground
Look, I'm not anti-AI. I use AI tools every single day. Claude Code is part of my daily workflow. I use MCP servers for documentation lookups. I generate boilerplate, refactor code, and explore unfamiliar APIs with AI assistance constantly.
But there's a difference between AI-assisted engineering and vibe coding. The difference is ownership.
When I use Claude Code to generate a starting point for a new feature, here's what actually happens. Say I'm building a new endpoint in our Spring Boot backend that handles event RSVPs. I'll describe the feature. Claude generates the controller, service, and repository layers. Then I read every line. I check the validation logic. I look at the error responses. I ask myself: what happens if someone RSVPs twice? What happens if the event is already full? What if the event was deleted between the time the user loaded the page and when they clicked RSVP?
I modify the generated code. I add error handling the AI missed. I rename variables to match our conventions. I write tests that cover the edge cases I can think of. I run the existing test suite to make sure nothing broke. Then I commit.
The AI saved me maybe 20 minutes of typing. But every decision in that code is mine. I understand every line. If it breaks at 3 AM, I can debug it. I know what it does and why.
That's AI-assisted engineering. The tool serves me. I don't serve the tool.
Vibe coding is the opposite. You serve the tool. You're along for the ride, accepting output you don't fully understand, hoping it works. And for a throwaway script, that's fine. For production software that real people depend on, it's not.
A Note for Junior Developers
If you're early in your career, I know the temptation. AI tools can make you feel productive immediately. You can build things that look impressive without understanding the underlying systems. And there's nothing wrong with using AI to learn. Generating code, reading it, understanding it, modifying it. That's a valid learning strategy.
But if you skip the understanding part, you're building on sand. The first time you hit a real production bug, a race condition, a memory leak, a deadlocked database transaction, you'll need skills that vibe coding doesn't teach. You'll need to read stack traces. You'll need to understand what the garbage collector is doing. You'll need to reason about state and concurrency and network reliability.
Those skills come from writing code, reading code, breaking code, fixing code. There's no shortcut. The AI tools will be better in two years than they are now. But your ability to use them well depends entirely on how deeply you understand the fundamentals.
Learn how a hash map works. Learn why database indexes matter. Learn what happens during a TCP handshake. Learn how your framework handles dependency injection under the hood. Learn to read code written by people who are better than you. These things compound over a career in a way that prompting skills don't.
AI tools will still be there when you're ready to use them well. They'll actually be more useful to you then, because you'll know when to trust the output and when to question it. You'll know what good code looks like. You'll be able to use AI as a multiplier on real skills instead of a replacement for skills you never built.
Where This Leaves Us
Vibe coding is real, it's here, and it's not going away. For quick prototypes, personal projects, and exploring ideas, it's great. I use it myself for exactly those things.
But let's not confuse it with engineering. Engineering is understanding what you're building, why you're building it that way, and what happens when things go wrong. No amount of prompting replaces that understanding.
Use the tools. They're good. Just make sure you're the one driving.
Comments (0)