The difference between good AI output and garbage is your prompt. Same model, same task – but a well-structured prompt gets working code on the first try while a vague prompt gets something you’ll spend 30 minutes fixing. Here are the patterns that work.
The 5 Elements of a Perfect Code Prompt
1. Context (What exists already)
"I have a Next.js 14 app using App Router, TypeScript, Tailwind CSS, and Supabase for the database. The user model has: id, email, name, created_at."
Without context, AI guesses your stack. With context, it generates code that fits.
2. Task (What to build)
"Create an API route that handles user profile updates. It should accept PATCH requests with name and email fields."
Be specific about the METHOD, the INPUTS, and the OUTPUTS.
3. Constraints (What rules to follow)
"Validate that email is a valid format. Return 401 if user is not authenticated. Use the existing Supabase client from @/lib/supabase. Follow the project's error handling pattern (try/catch with NextResponse.json)."
Constraints prevent AI from making its own architectural decisions.
4. Examples (What good output looks like)
"Follow the same pattern as the existing /api/posts/route.ts file. Use the same response format: { success: true, data: ... } or { success: false, error: '...' }"
Reference existing code. AI mimics what you show it.
5. Edge Cases (What could go wrong)
"Handle: empty body, missing fields, duplicate email, database connection error, user not found."
If you don’t mention edge cases, AI won’t handle them.
The Full Prompt Template
[CONTEXT]
Project: {stack, structure, relevant files}
Existing patterns: {how similar things are done}
[TASK]
Build: {specific feature}
Inputs: {what it receives}
Outputs: {what it returns}
[CONSTRAINTS]
- Follow {pattern} from {existing file}
- Use {specific library/approach}
- Don't {common mistake to avoid}
[EDGE CASES]
- Handle {error 1}
- Handle {error 2}
- Handle {error 3}
Bad vs Good Prompts (Real Examples)
Example 1: API Endpoint
Bad: “Write a login API”
Good: “Write a POST /api/auth/login endpoint for my Next.js app. It should accept {email, password} in the body, validate against Supabase Auth, return {success: true, user: {id, email, name}} on success or {success: false, error: string} on failure. Include rate limiting check (max 5 attempts per IP per minute). Use the existing pattern from /api/auth/signup/route.ts.”
Example 2: React Component
Bad: “Make a form component”
Good: “Create a React component for hackathon registration. Fields: team name (required, max 50 chars), team size (dropdown: 2-6), hackathon theme (radio: AI, Web, Mobile, Other). On submit, POST to /api/teams/register. Show loading state during submission. Show success toast on 201, error toast on 4xx. Use shadcn/ui form components and react-hook-form for validation. Mobile responsive.”
Advanced Techniques
Chain of Thought (for complex logic)
"Before writing code, explain your approach:
1. What's the data flow?
2. What are the edge cases?
3. What's the error handling strategy?
Then write the implementation."
Iterative Refinement
First: "Write the basic version"
Then: "Add error handling"
Then: "Add input validation"
Then: "Add tests"
Build up in layers instead of asking for everything at once.
Negative Prompting
"Do NOT use class components. Do NOT use any in TypeScript. Do NOT import from relative paths - use the @ alias. Do NOT add console.log statements."
Tell AI what to avoid. It’s surprisingly effective.
The Hackathon Speed Hack
At a hackathon, your prompts should front-load context once, then be specific per task:
First message: "Here's my project structure, stack, and conventions: [paste]"
Every subsequent message: "Now build [specific feature] following those patterns."
The AI remembers the context. You just give tasks.
Vibe coding guide | AI coding tools | Practice at hackathons