Vibing with the Machine : Practical advice for vibe-coding
I have to admit, two years ago I didn’t think we’d be writing code in English anytime soon. I wasn’t the only one, the more you’ve been coding, the harder it is to believe a computer could do your job. I started using ChatGPT as a self-teaching tool, then it started making me plots, code snippets and prototypes, and now here we are. If you haven’t tried Claude code, Antigravity, or another vibe-coding IDE, I suggest giving them a shot. Just ask for a React app, a simple game, or anything else that interests you.
I remember being shocked at how good these tools are at producing a working prototype, sometimes in a single prompt. Then, you start to come back down to earth. The AI makes mistakes, and usually not obvious ones. It will reach for quick solutions when real features are needed, fall into anti-patterns and fail to see the bigger picture. It doesn’t always find the right way to abstract or encapsulate parts of the code, and will change random files in your codebase. It also regularly deletes or corrupts files, though I suspect that is temporary.
If you’re careless and vague, you risk your code looking like an unsolvable mess after a few prompts. However, if you have intent and direction, you can be more productive than you ever thought you could be. The more I use these tools, the more I like them. I’ve definitely gotten better at getting what I want from LLMs over the past few months. Today I’ll try to convince you that vibe-coding is a skill worth investing in, and tell you what I've learned while practicing.
First, I don't think you can be an effective vibe-coder if you don’t have at least some idea of how that code is being generated. LLMs are state machines, well, not precisely, but it’s a useful way to think about them. The state of the LLM is defined by its context, the text information that you or the tools you use provide as input. Your job as a newly promoted prompt engineer is to manage that state so the model can output correct, useful, and maintainable code as often as possible.
Make no mistake, it is a skill. LLMs become less astute as the context gets closer to a fraction of their total window. Keeping the context filled only with relevant and concise information is a constant tradeoff. If you give only lazy, broad directions, the LLM will give you the bare minimum. If you give extremely detailed design directions, you may end up spending more time than if you had just coded it yourself. The new art is to know what to ask for and how. Here is what I found useful to keep in mind:
Context is for your application. I've seen people try to create “Data Science” or “DevOps” LLM agents by pasting hundreds of lines of textbook material into them. It doesn’t really work like that. SOTA LLMs have likely already been trained on that material, and on many other texts like it. It’s not a human learner, it doesn’t need a refresher. Agents become specialized through orchestration. One agent breaks a plan into tasks and hands those tasks off to other agents, not because they are better at it, but because they have more managable information to work with.
Think about your file structure. Splitting your code into small, modular components helps the LLM figure out which function needs to be called while keeping only the relevant information in context. Unless the function is actually in the context, or can be found from it, the LLM will not magically know about it. Being organized also helps when pointing the LLM to specific files and functions when giving directions.
Make a plan, or at least read the plan. Planning is what moves LLM-based development from a nice demo to a working product. The better the plan, the better the output. In fact, the plan is even generated for you now, and new tools make it very easy to annotate and revise it. You also absolutely still need to understand what is being implemented. As the engineer, you are the keeper of the codebase, and are still liable for the generated code. I find useful to maintain a design doc as I build, which allows me to mention it any time I start a new instance.
Be strategically detailed. The LLM is a reflection of the data it was trained on. You can think of it as an advanced form of search and copy-paste. The more often it has seen similar information, the more readily it will do what used to be your job. This also means you can afford to give broader directions for things like Dockerfiles, CMake configs, CI/CD pipelines, server setup, and so on. There are almost no reasons to write these by hand anymore, unless you’re doing it for fun. On the other extreme, you have application-specific or optimized low-level code. In those cases you’ll need to give very detailed instructions, link prototypes, or write pseudo-code to guide the LLM effectively. Most code sits somewhere in between, the first thing I'll take the lead on is defnining function interfaces and data structures.
Ask for tests, it barely costs anything. Tests are my favorite use case for LLM-driven development. First, because it's often a rather tedious task, and second, because it will not directly impact the functionality of the codebase. Writing tests is always safe to do for LLMs, it is AI kindergarten. It's generally easy to read tests if you already know what the code is doing, and it is even more important to catch errors quickly now that code is generated automatically. Even if you enjoy writing code by hand, letting an LLM handle the tests is probably the fastest way to boost your productivity.
Git is the most important vibe-coding tool. LLMs will sometimes corrupt files when making modifications, but even if they didn't, you'd still need to commit religiously. Since you're responsible for code you didn't write, subtle issues will arise that you might not catch immediately. LLMs are not forward thinkers, and they will touch files they didn't have to. Sometimes, you have to throw in the towel, checkout, and go again. My advice is simple: if a line changes and the code seems to be working, commit. Squash commits whenever you hit a milestone, ask for tests, and repeat.
If you enjoy managing, planning, and reading code, vibe-coding will feel natural to you. It pushes you to think about systems and interfaces first and implementation second. It values what you know about software engineering rather than the act of typing code. It rewards organization, structure, good design and architecture. It’s also an incredible learning tool. You can build, or clone anything and ask for an explanation afterwards. I personally enjoy focusing on writing conceptually correct instructions rather than syntactically correct ones. It’s some of the most fun I’ve ever had coding, and I can't wait to do it tomorrow.