AIDevStart
HomeDirectoryModelsListsRankingsComparisonsGuidesBlogLearn AI Dev
Submit Tool
AIDevStart

Empowering developers with curated AI tools across the entire stack.

Some links on this site are affiliate links. We may earn a commission at no extra cost to you. Learn more.

DirectoryListsRankingsComparisonsGuidesBlogPrivacyTermsCookiesDisclosure

© 2026 AIDevStart. All rights reserved.

Back to Guides

Prompt Engineering for Developers: Write Better AI Prompts

Intermediate45 min

Learn proven prompt engineering techniques to get dramatically better code from AI tools like Cursor, Copilot, and Claude. Includes templates and real examples.

Prompt Engineering for Developers: Write Better AI Prompts

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.


Why Prompt Engineering Matters

Most developers send prompts like this:

"Fix this bug"

Developers who get great results send prompts like this:

"This calculateTax function returns NaN when price is 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.


The CRISP Framework

Use this framework for any coding prompt:

  • Context — What is this code part of?
  • Requirement — What exactly do you need?
  • Input/Output — What goes in? What should come out?
  • Style — What patterns/conventions should it follow?
  • Pitfalls — What should it avoid?

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

Technique 1: Chain-of-Thought Prompting

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.


Technique 2: Few-Shot Examples

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

Technique 3: System Prompts (via .cursorrules or Claude system message)

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

Technique 4: The "Rubber Duck" Prompt

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]

Technique 5: Test-Driven Prompting

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.


Technique 6: Iterative Refinement

Never accept the first output without review. Use this loop:

  1. Generate — "Write a function to paginate database results"
  2. Critique — "What are the potential issues with this implementation?"
  3. Refine — "Fix the N+1 query problem you identified"
  4. Harden — "Add input validation and error handling"

Common Mistakes to Avoid

MistakeInstead
"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 contextInclude the file, function, and relevant data structures
Huge scopeBreak large tasks into specific sub-tasks

Prompt Templates

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].

Next Steps

  • Read our Cursor AI Guide to apply these techniques with Cursor
  • Explore the OpenAI Prompt Engineering Guide for more advanced techniques
  • Check out our tool comparisons to find the AI that responds best to these prompts