The same AI coding assistant can feel like a senior engineer or like a confused intern, and most of the difference is how you talk to it. After shipping a stack of apps this way, these are the prompting habits that consistently get good code instead of plausible-looking garbage. None of them are clever tricks. They are just good delegation.
Describe the outcome, not the implementation
Amateur prompt: “add a useState and a button that calls saveDraft.” Better prompt: “users should be able to save a draft and come back to it later.” Tell it what should be true for the user, and let it propose the implementation. You will often get a cleaner approach than the one you would have dictated, because you stopped constraining it to your first idea, and you stay focused on the product instead of the syntax.
The deeper reason this works: when you specify the implementation, you cap the quality at your own first guess. When you specify the outcome, the model can bring patterns you did not think of. You are hiring it for its breadth, so do not handcuff it to your narrowest instruction.
Give it the context it cannot see
The model does not know your stack, your conventions, or last week’s decisions unless you tell it. Most “the AI did it wrong” moments are really “I did not give it the context to do it right.” Up front, hand it:
- the language and framework, and the key libraries you are using,
- the relevant files or patterns to follow, for example “match how
auth.tsdoes this,” - any hard constraints, like “no new dependencies” or “this must work offline.”
Context is the cheapest quality improvement available. Thirty seconds of setup prevents an hour of fixing a technically-correct answer that ignored how your project actually works.
Work in small, reviewable slices
Ask for one thing at a time. A single function, screen, or behavior produces a diff you can actually read and verify. Giant “build the entire feature” prompts produce output you will either rubber-stamp, which is dangerous, or spend longer untangling than you would have spent writing it yourself, which defeats the point. Small slices keep you in control and keep the AI’s mistakes small and obvious instead of large and buried.
Show, do not just tell
If you have an existing pattern, point at it: “do this the same way the settings screen handles state.” Concrete examples constrain the model far better than adjectives ever will. “Make it clean” means nothing, because the model has no idea what clean means to you. “Follow the structure of this file” means everything, because now it has a target. When in doubt, give it an example to imitate rather than a quality to aspire to.
State the constraints and the “do not"s
Models default to adding things, more libraries, more abstraction, more cleverness. If you do not want that, say so explicitly: “do not add a dependency,” “do not change the public API,” “keep this component presentational.” A few clear guardrails prevent most of the scope creep that quietly bloats a project over time. The model is not going to guess your boundaries, so draw them.
Ask it to explain, not just produce
When a change is non-trivial, ask “why this approach?” or “walk me through what this does.” You get two wins. First, you catch flawed reasoning before it ships, while it is still cheap to fix. Second, you actually learn the code you are responsible for, instead of accumulating mystery. And if the explanation comes back hand-wavy or contradictory, that is a loud signal to slow down and look harder, because the model may be papering over something it does not actually understand either.
Treat the first output as a draft
The first response is a starting point, not a verdict. “Good, but handle the empty case.” “Simplify this, it is overengineered.” “That works, now do the boring version.” Iteration is the normal, expected path, not a sign you prompted wrong. Some of the best results come from three rounds of “almost, but” rather than one perfect prompt, so do not treat the first answer as final just because it looks finished.
Keep a context file you reuse
Maintain a short document, your stack, your conventions, the things to never do, and feed it at the start of sessions. It stops you from re-explaining yourself every time and stops the model from re-introducing the same mistakes you corrected yesterday. It is the single highest-leverage prompt you will write, because unlike every other prompt, you write it once and it improves every session that follows.
When the AI gets stuck, change the question
Sometimes the assistant loops, producing slight variations of the same broken thing. That is your cue to stop repeating the prompt and change it. Give more context, break the task smaller, paste the actual error, or describe the goal a different way. Hammering the same prompt harder rarely works. Reframing it almost always does, because the model was missing information, not effort.
Verify before you trust
One habit underpins all of the above: never let good prose lull you into skipping verification. A confident, well-formatted answer is not evidence that the code is correct, only evidence that the model writes fluently, which it always does. So after every non-trivial change, run it, read it, and check the part that actually matters against reality: the real API, the real data, the real edge case. The assistant’s job is to produce a strong draft, fast. Your job is to be the one who confirms it is actually true before it ships. Keep that division of labor sharp and the speed of vibe coding never curdles into a steady stream of confident, plausible bugs.
The mindset
Good prompting is just good delegation: a clear outcome, enough context, a tight scope, and verification at the end. Treat the assistant like a fast junior developer you trust to draft but not to decide, and you will get most of the speed of vibe coding with little of the mess. The broader philosophy behind all of this is in what vibe coding actually is, and the specific traps to avoid are in 9 vibe coding mistakes.