Learn proven prompt engineering techniques to get dramatically better code from AI tools like Cursor, Copilot, and Claude. Includes templates and real examples.
The difference between a mediocre AI output and a perfect one is often just the quality of the prompt. This guide covers the techniques used by top engineers to get consistently excellent results.
Most developers send prompts like this:
"Fix this bug"
Developers who get great results send prompts like this:
"This
calculateTaxfunction returns NaN whenpriceis undefined. The function should return 0 in that case. The input is always a number or undefined, never null. Fix it without changing the function signature."
The second prompt is 5× more specific — and gets 10× better results.
Use this framework for any coding prompt:
Example (without CRISP):
Write a rate limiter
Example (with CRISP):
Write a rate limiter for a Next.js API route.
- It should use an in-memory sliding window (no Redis)
- Limit: 10 requests per minute per IP address
- Input: request IP address (string)
- Output: { ok: boolean, remaining: number, retryAfterMs: number }
- Use TypeScript with strict types
- Don't use any external packages
Ask the AI to reason step-by-step before writing code:
Before writing the code, think through:
1. What edge cases exist?
2. What's the most efficient algorithm?
3. What TypeScript types are needed?
Then write a function that debounces API calls with a configurable delay.
Why it works: The AI's reasoning process improves when it "thinks out loud" before generating code — just like a human developer.
Show the AI the pattern you want by providing examples:
Convert these REST endpoints to tRPC procedures following the same pattern:
Example:
// REST: GET /api/users/:id
// tRPC:
getUserById: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.user.findUnique({ where: { id: input.id } });
}),
Now convert these:
- POST /api/posts (body: title, content, authorId)
- DELETE /api/posts/:id
- GET /api/posts?authorId=xxx
Define persistent rules that apply to every interaction:
You are a TypeScript expert. When writing code:
- Always use strict TypeScript — never use 'any'
- Prefer functional patterns (map, filter, reduce) over loops
- Write JSDoc comments for public functions
- Handle errors explicitly — never swallow exceptions
- Prefer composition over inheritance
- Keep functions under 20 lines; extract helpers if needed
Ask the AI to explain your code to find bugs — it often discovers issues during explanation:
Explain exactly what this function does, line by line.
Point out any potential bugs, edge cases, or performance issues you notice
while explaining it.
[paste your function here]
Write the test first, then ask AI to implement the function:
Here are the tests that must pass:
test('validates email format', () => {
expect(validateEmail('user@example.com')).toBe(true)
expect(validateEmail('invalid')).toBe(false)
expect(validateEmail('')).toBe(false)
expect(validateEmail('user@')).toBe(false)
})
Implement the validateEmail function in TypeScript.
Do not modify the tests.
Why it works: Concrete acceptance criteria remove ambiguity completely.
Never accept the first output without review. Use this loop:
| Mistake | Instead |
|---|---|
| "Fix this" | "Fix the null pointer exception on line 47 when user is undefined" |
| "Make it better" | "Reduce the time complexity from O(n²) to O(n)" |
| "Add auth" | "Add JWT authentication to the /api/profile endpoint using the existing verifyToken middleware" |
| No context | Include the file, function, and relevant data structures |
| Huge scope | Break large tasks into specific sub-tasks |
Copy these and adapt for your use case:
Debug Prompt:
Bug: [describe symptom]
Expected: [what should happen]
Actual: [what happens instead]
Reproduction: [how to trigger it]
Suspected cause: [your hypothesis, if any]
Refactoring Prompt:
Refactor this code to [goal].
Constraints:
- Keep the same public API (don't change function signatures)
- Maintain the existing test coverage
- Use [pattern/library]
Don't change the functionality, only the implementation.
New Feature Prompt:
Add [feature] to this existing codebase.
The existing pattern for similar features is [describe or attach example].
Use the same conventions (naming, error handling, testing style).
The new code should integrate with [existing component/service].