Most developers write prompts like text messages. Short, vague, and missing context. Then they wonder why AI generates code that misses the point.
The difference between a prompt that produces usable code and one that produces garbage is not magic. It is structure. Research shows that well-structured prompts produce code that works on the first try significantly more often. And the sweet spot is shorter than you think — 150 to 300 words.
If you have read Context Engineering — The Most Important AI Coding Skill, you already know how to set up your project context. This article focuses on what you type into the prompt itself.
The Anatomy of a Good Code Prompt
Every effective code prompt has four parts. You do not need a fancy framework name to remember them. Just think: Task, Context, Constraints, Format.
Task — What do you want the AI to do?
Context — What does the AI need to know about your project, the existing code, or the problem?
Constraints — What should the AI avoid? What are the boundaries?
Format — What should the output look like?
Here is a bad prompt:
Write a function to validate emails
Here is the same prompt, structured properly:
Write an email validation function in TypeScript.
Context: This is for a user registration form in a Next.js app.
We use Zod for validation throughout the project.
Constraints:
- Use Zod schema, not regex
- Must handle edge cases: empty string, missing @, multiple @
- Do not use any external email validation library
- Must return a typed error message, not just true/false
Format: Export the Zod schema and a validate function separately.
Include 5 unit test cases using Vitest.
The second prompt gives AI everything it needs. The first one leaves AI guessing about the language, the approach, the edge cases, and the testing strategy.
Few-Shot Prompting — Show, Don’t Just Tell
Few-shot prompting means giving examples of what you want before asking for the output. For code generation, this is one of the highest-value techniques available.
Instead of describing what you want, you show it.
Prompt:
I need to add a new API endpoint following our existing pattern.
Here is how our existing endpoints look:
// GET /api/users
export async function getUsers(req: Request): Promise<Response> {
const users = await userService.findAll();
return json({ data: users, count: users.length });
}
// GET /api/users/:id
export async function getUser(req: Request): Promise<Response> {
const { id } = req.params;
const user = await userService.findById(id);
if (!user) return json({ error: "User not found" }, 404);
return json({ data: user });
}
Now create the endpoints for the "projects" resource following
the same pattern. Projects have: id, name, description, ownerId,
createdAt. Include GET all, GET by id, POST create, PUT update,
and DELETE.
The AI now matches your exact coding style, error handling pattern, and response format. Without the examples, it would generate code that technically works but does not match your codebase.
When to use few-shot prompting:
- When you have an established pattern and want AI to follow it
- When you need code that matches an existing codebase style
- When the output format is specific and hard to describe in words
Tip: Three to five examples is the sweet spot. More than that uses too many tokens without improving quality.
Chain-of-Thought for Debugging
When you paste an error and ask AI to “fix this,” you often get a band-aid fix that hides the real problem. Chain-of-thought prompting forces the AI to reason through the problem before jumping to a solution.
Bad prompt:
Fix this error:
TypeError: Cannot read properties of undefined (reading 'map')
Good prompt:
I am getting this error in my React component:
TypeError: Cannot read properties of undefined (reading 'map')
The error happens on line 15: {users.map(user => <UserCard key={user.id} user={user} />)}
The component fetches users from /api/users on mount.
Before suggesting a fix:
1. Explain what could cause 'users' to be undefined
2. List all possible timing scenarios (loading, error, empty)
3. Then suggest the fix that handles all cases
The chain-of-thought instruction (“Before suggesting a fix, explain…”) makes the AI think through the problem systematically. It often catches issues that a quick fix would miss — like race conditions or missing error states.
This technique is especially powerful for complex bugs. When the AI explains its reasoning, you can spot where it goes wrong. If it misunderstands the problem at step 1, you correct it early instead of getting a wrong fix.
Role Prompting — Set the Right Perspective
Role prompting tells the AI to approach the task from a specific perspective. This is not about making the AI pretend to be someone. It is about activating relevant knowledge.
Security review:
Review this authentication code as a security engineer.
Focus on: SQL injection, XSS, CSRF, token handling,
and common auth vulnerabilities.
Do not comment on code style or naming.
Performance optimization:
Analyze this database query as a DBA.
The users table has 2 million rows.
The query runs on every page load.
Suggest index changes and query optimizations.
Code review:
Review this pull request as a senior developer on the team.
Our standards: TypeScript strict mode, no any types,
Result type for errors, async/await only.
Flag violations and suggest fixes.
The role focuses the AI on what matters. Without it, a security review might waste time on variable naming. A performance analysis might suggest readability improvements instead of index optimizations.
Negative Prompts — Tell AI What NOT to Do
One of the most underused techniques is telling the AI what to avoid. AI models are trained on millions of code examples, and they default to the most common patterns. If those patterns do not fit your project, you need to say so.
Example:
Create a user authentication system in Python.
Do NOT:
- Use Flask-Login (we use custom middleware)
- Store passwords in plain text (use bcrypt)
- Use session-based auth (we use JWT)
- Use global variables for configuration
- Use print() for logging (use the logging module)
- Add comments explaining basic Python syntax
Every “do not” prevents a common default that would require manual fixing later. Without these constraints, AI might generate a working auth system that uses Flask-Login, sessions, and print statements — all technically correct but wrong for your project.
Common negative prompts for code:
- “Do not use deprecated APIs” — prevents AI from using older patterns it was trained on
- “Do not add dependencies not already in package.json” — prevents surprise installations
- “Do not use any type in TypeScript” — enforces strict typing
- “Do not mock the database in integration tests” — ensures real integration testing
Iterative Refinement — The Multi-Turn Strategy
The biggest mistake developers make is trying to get everything in one prompt. Complex code rarely comes out perfect on the first try. Iterative refinement is the process of building up the solution across multiple prompts.
Step 1 — Get the structure:
Create a REST API for a notes app using Express and TypeScript.
Just the project structure and route definitions.
Do not implement the handlers yet.
Step 2 — Implement one piece:
Now implement the POST /notes handler.
It should validate the request body with Zod,
save to PostgreSQL using Prisma, and return the created note.
Follow the error handling pattern from the existing code.
Step 3 — Add edge cases:
Add error handling for:
- Invalid JSON body
- Missing required fields
- Database connection failure
- Duplicate title (unique constraint violation)
Return appropriate HTTP status codes for each.
Step 4 — Add tests:
Write integration tests for the POST /notes endpoint.
Test all the success and error cases we just implemented.
Use the test database configuration from our test setup.
Each step builds on the previous one. The AI has the full context of what it already generated. You can review at each step and correct course before moving forward.
This works much better than one giant prompt because:
- You catch mistakes early
- The AI focuses on one thing at a time
- You maintain control over the direction
- Each step is small enough to review properly
Tool-Specific Prompt Differences
Different AI coding tools respond better to different prompt styles.
Claude Code (Terminal Agent)
Claude Code excels at conversational, multi-turn prompts. You can be informal and iterate naturally.
Look at the auth middleware in src/middleware/auth.ts.
It does not handle expired tokens properly.
When a token expires, it should return 401 with a
clear error message, not 500.
Can you also check if there are other places in the
codebase that handle token expiration? I want to make
sure we are consistent.
Claude Code reads your files, runs commands, and checks the codebase. You do not need to paste code — just reference file paths.
Cursor (Editor Agent)
Cursor works best with file-focused prompts. Use @file references to point it at specific code.
@src/services/userService.ts
@src/types/user.ts
Add a deleteUser method to the user service.
It should soft-delete by setting deletedAt to now.
Follow the same pattern as updateUser.
Cursor’s Composer mode handles multi-file changes well, so you can reference multiple files and ask for coordinated changes.
GitHub Copilot (Inline)
Copilot responds best to comments in your code. Write a comment describing what comes next, and Copilot completes it.
// Validate the email format using Zod schema
// Return { valid: boolean, error?: string }
Keep Copilot comments specific. “Validate email” is vague. “Validate email format using Zod schema, return valid boolean and optional error string” gives Copilot enough to generate the right code.
Prompt Anti-Patterns to Avoid
After working with AI coding tools daily, these are the prompts that consistently produce bad results.
The kitchen sink prompt:
Build me a complete e-commerce platform with user auth,
product catalog, shopping cart, payment processing,
order management, admin dashboard, email notifications,
and a recommendation engine.
Too much at once. The AI will generate shallow code that barely works for any feature. Break it into 20 separate prompts.
The vague prompt:
Make this code better.
Better how? Faster? More readable? More secure? The AI will guess, and it will often guess wrong.
The copy-paste-everything prompt:
Here is my entire 500-line file. Fix the bug.
Point to the specific area. Tell the AI which function has the bug. Describe the expected vs actual behavior. The AI does better with focused context.
The no-context prompt:
Write a login function.
In what language? Using what framework? With what kind of authentication? For what kind of application? Every missing detail is a guess the AI will make.
Putting It All Together
Here is a complete example that combines multiple techniques. This is how an experienced developer prompts AI for a real task.
I need to add rate limiting to our Express API.
Context:
- Express 5 with TypeScript
- Redis is already set up (see src/lib/redis.ts)
- Auth middleware runs before routes (see src/middleware/auth.ts)
- We have 3 tiers: free (100 req/hour), pro (1000 req/hour),
enterprise (unlimited)
Constraints:
- Use our existing Redis connection, do not create a new one
- Use sliding window algorithm, not fixed window
- Do not use express-rate-limit package (we need custom logic
for our tier system)
- Return standard rate limit headers (X-RateLimit-Limit,
X-RateLimit-Remaining, X-RateLimit-Reset)
- Return 429 with a JSON error body when limit is exceeded
Please:
1. First explain the sliding window algorithm briefly
2. Then implement the middleware
3. Then show how to add it to the route pipeline
4. Include unit tests for all three tiers
Follow our existing middleware pattern in src/middleware/.
This prompt uses task, context, constraints, negative prompts, chain-of-thought, and format specifications. It gives the AI everything it needs to produce production-ready code.
Key Takeaways
- Structure beats length. A 200-word structured prompt beats a 50-word vague one every time.
- Show, don’t just tell. Few-shot examples produce code that matches your codebase patterns.
- Force the AI to think first. Chain-of-thought prompting catches deeper bugs.
- Say what to avoid. Negative prompts prevent the most common wrong defaults.
- Iterate in steps. Build complex features across multiple prompts, not one giant request.
What’s Next?
In MCP in Practice — Connecting AI to Your Tools, you will learn how to extend your AI tools with real-world connections to databases, GitHub, and custom tools. Good prompts become even more powerful when AI has access to your actual data.
Part 9 of the Vibe Coding series.