Prompt Engineering For AI Coding Prompt Engineering For AI Coding

The Complete Guide to AI Coding Prompts 2025

Master prompt engineering for AI coding tools. Learn proven techniques, examples, and anti-patterns to get 10x better code from Cursor, Copilot, and ChatGPT.

I wasted two months using AI coding tools the wrong way.

I’d type “create a login form” and get mediocre code. I’d ask “fix this bug” and the AI would be confused. I thought the tools weren’t as good as everyone claimed.

Then I learned prompt engineering, and everything changed. Same tools, 10x better results.

The difference between developers who love AI coding assistants and those who hate them isn’t the tool they’re using—it’s how they ask. A well-crafted prompt gets you production-ready code in seconds. A bad prompt wastes time and generates garbage.

After writing thousands of prompts across Cursor AI, GitHub Copilot, ChatGPT, and Claude, I’ve identified patterns that consistently work. This guide will show you the exact formulas I use every day.


Quick Start Summary

Visual Framework  for Effective Code Prompts

What You’ll Learn:
– The anatomy of an effective code prompt
– Specific vs vague: The #1 factor in prompt quality
– Context provision techniques
– Prompt templates for common tasks
– Platform-specific tips (Cursor vs Copilot vs ChatGPT)
– Anti-patterns to avoid

Prerequisites:
– Experience with at least one AI coding tool
– Basic understanding of your programming language
– Willingness to be more specific than you think necessary

Key Insight: The best prompts are specific, contextual, and constrained. Vague prompts = vague code.


The Fundamental Principle of Code Prompts

The Specificity Rule

Bad prompt (vague):

Create a form

Good prompt (specific):

Create a React login form with:
- Email input (validated with regex)
- Password input (min 8 chars, show/hide toggle)
- "Remember me" checkbox
- Submit button (disabled while loading)
- Error message display below form
- Uses Tailwind CSS for styling
- Form validation with react-hook-form
Code language: JavaScript (javascript)

Why Specificity Matters

AI coding models are trained on millions of code examples. When you say “create a form,” the AI has to guess:
– What framework? (React? Vue? Angular? Vanilla JS?)
– What styling? (CSS? Tailwind? Material UI?)
– What validation? (Built-in? Library? None?)
– What fields? (Login? Signup? Contact?)

The AI will make reasonable guesses, but they’re rarely what you actually want.

The more specific you are, the less the AI has to guess.


Anatomy of a Perfect Code Prompt

The 5-Part Framework

Every great code prompt has these five elements:

[1. ACTION] + [2. CONTEXT] + [3. REQUIREMENTS] + [4. CONSTRAINTS] + [5. STYLE]
Code language: CSS (css)

1. ACTION – What to do

Be explicit about the operation:
– “Create” / “Build” / “Generate” (new code)
– “Refactor” / “Rewrite” / “Optimise” (modify existing)
– “Fix” / “Debug” / “Resolve” (fix issues)
– “Explain” / “Document” / “Comment” (add documentation)
– “Test” / “Validate” / “Check” (testing/QA)

2. CONTEXT – What exists

Provide relevant information:
– Current code structure
– File locations
– Existing patterns
– Related components
– Tech stack details

3. REQUIREMENTS – What it must do

Functional specifications:
– Features list
– User interactions
– Data flow
– API endpoints
– Expected behavior

4. CONSTRAINTS – What it can’t do

Limitations and boundaries:
– Performance requirements
– Security considerations
– Compatibility needs
– Don’t break existing code
– Specific libraries to use/avoid

5. STYLE – How it should look

Code style and patterns:
– Follow existing code style
– Use specific design patterns
– Match other files
– Naming conventions
– Comment style

Real Example Using All 5 Parts

[ACTION]
Create a user authentication service

[CONTEXT]
In our Express.js API (src/services/), we use:
- PostgreSQL with Prisma ORM
- JWT tokens for auth
- bcrypt for password hashing

[REQUIREMENTS]
The service should provide:
- register(email, password): Creates user, returns JWT
- login(email, password): Validates credentials, returns JWT
- verifyToken(token): Validates JWT, returns user data
- refreshToken(token): Issues new JWT from valid refresh token

[CONSTRAINTS]
- Passwords must be hashed with bcrypt (10 rounds)
- JWTs expire in 15 minutes, refresh tokens in 7 days
- Validate email format with regex
- Don't expose password hashes in responses
- Handle all errors gracefully (no unhandled rejections)

[STYLE]
- Use async/await (no callbacks)
- Follow the same pattern as src/services/UserService.ts
- Add JSDoc comments for all public methods
- Use TypeScript interfaces for all parameters
Code language: PHP (php)

This prompt will generate near-perfect code on the first try.


Proven Prompt Templates

Template 1: Creating a New Component

Create a [FRAMEWORK] [COMPONENT_TYPE] component for [PURPOSE]:

Features:
- [Feature 1]
- [Feature 2]
- [Feature 3]

Props:
- [prop1]: [type] - [description]
- [prop2]: [type] - [description]

Styling:
- Use [STYLING_LIBRARY]
- Match the design of [REFERENCE_COMPONENT]

Example usage:
<ComponentName prop1="value" prop2={value} />
Code language: HTML, XML (xml)

Real example:

Create a React UserCard component for displaying user profiles:

Features:
- Shows avatar, name, email, and role
- Clickable to navigate to user detail page
- Hover effect with subtle shadow
- Loading state with skeleton UI

Props:
- user: User - user object with id, name, email, avatar, role
- onClick?: () => void - optional click handler
- loading?: boolean - shows skeleton if true

Styling:
- Use Tailwind CSS
- Match the design of PostCard.tsx in same folder

Example usage:
<UserCard user={userData} onClick={() => navigate('/user/123')} />
Code language: JavaScript (javascript)

Template 2: Adding an API Endpoint

Add a [HTTP_METHOD] [ENDPOINT] endpoint in [FILE]:

Purpose: [What this endpoint does]

Request:
- Method: [GET/POST/PUT/DELETE]
- Path: [/api/path/:param]
- Body: [JSON structure]
- Query params: [?param=value]

Response:
- Success (200): [JSON structure]
- Error (4XX): [Error format]

Logic:
1. [Step 1]
2. [Step 2]
3. [Step 3]

Validation:
- [Validation rule 1]
- [Validation rule 2]

Follow the pattern of existing routes in [REFERENCE_FILE]
Code language: CSS (css)

Real example:

Add a POST /api/comments endpoint in src/routes/comments.ts:

Purpose: Create a new comment on a blog post

Request:
- Method: POST
- Path: /api/comments
- Body: { postId: string, content: string }
- Headers: Authorization: Bearer <token>

Response:
- Success (201): { id, postId, userId, content, createdAt }
- Error (400): { error: "Validation failed", details: [...] }
- Error (401): { error: "Unauthorized" }

Logic:
1. Verify JWT token and extract userId
2. Validate postId exists in database
3. Validate content (min 1 char, max 500 chars)
4. Create comment in database
5. Return created comment with user info

Validation:
- content must be 1-500 characters
- postId must be valid UUID
- User must be authenticated

Follow the pattern of existing routes in src/routes/posts.ts
Code language: JavaScript (javascript)

Template 3: Refactoring Code

Refactor [FILE/FUNCTION] to [IMPROVEMENT]:

Current issues:
- [Problem 1]
- [Problem 2]

Target improvements:
- [Improvement 1]
- [Improvement 2]

Requirements:
- Don't change the public API (same inputs/outputs)
- Maintain all existing functionality
- Add [new requirement if any]

Specific changes:
- [Change 1]
- [Change 2]

Real example:

Refactor src/utils/dataFetcher.ts to use async/await instead of callbacks:

Current issues:
- Nested callbacks causing callback hell
- Error handling is inconsistent
- Hard to test with current structure

Target improvements:
- Convert all functions to async/await
- Consistent try/catch error handling
- Return standardized { data, error } objects

Requirements:
- Don't change function names (used throughout app)
- Maintain the same input parameters
- Keep backward compatibility with existing code

Specific changes:
- fetchData(url, callback) → async fetchData(url)
- Add proper TypeScript return types
- Add JSDoc comments
Code language: PHP (php)

Template 4: Debugging/Fixing Issues

Fix [BUG_DESCRIPTION] in [FILE]:

Current behavior:
[What's happening now]

Expected behavior:
[What should happen]

Error message:
[Paste full error]

Relevant code:
[Paste code section]

Context:
[Environment, dependencies, related files]

Real example:

Fix infinite re-render loop in src/components/Dashboard.tsx:

Current behavior:
Component renders continuously, browser freezes, console shows "Maximum update depth exceeded"

Expected behavior:
Component should render once and only re-render when user data changes

Error message:
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

Relevant code:
useEffect(() => {
  setStats(calculateStats(userData));
}, [userData]);

function calculateStats(data) {
  return { total: data.length, active: data.filter(u => u.active) };
}

Context:
- React 18.2
- userData is passed as prop from parent
- calculateStats creates new object each time
Code language: PHP (php)

Template 5: Writing Tests

Generate [TEST_TYPE] tests for [FILE/FUNCTION]:

Test coverage needed:
- [Scenario 1]
- [Scenario 2]
- [Edge case 1]
- [Error case 1]

Testing setup:
- Framework: [Jest/Vitest/etc]
- Mocking: [What needs to be mocked]
- Test data: [Describe test fixtures]

Follow the pattern of [EXISTING_TEST_FILE]
Code language: CSS (css)

Real example:

Generate unit tests for src/utils/validateEmail.ts:

Test coverage needed:
- Valid email formats (user@domain.com, user+tag@domain.co.uk)
- Invalid formats (no @, no domain, special chars, spaces)
- Edge cases (empty string, null, undefined, very long emails)
- Returns true for valid, false for invalid

Testing setup:
- Framework: Jest
- No mocking needed (pure function)
- Test data: Array of valid/invalid email examples

Follow the pattern of tests/utils/validatePassword.test.ts
Code language: JavaScript (javascript)

Platform-Specific Prompt Tips

Cursor AI Prompts

Best practices for Cursor (Cmd+K and Composer):

  1. Always select code first (for Cmd+K)
    [Select function] → Cmd+K → "Add error handling"
  2. Mention file locations (for Composer)
    In src/components/Auth.tsx, add loading states
  3. Reference other files as examples
    Style this the same way as Dashboard.tsx
  4. Use “looking at” for context
    Looking at UserService.ts, create a similar PostService.ts
  5. Be explicit about multi-file changes
    Update both src/routes/api.ts and src/types/api.ts

GitHub Copilot Prompts

Best practices for Copilot:

  1. Write comments above where you want code
    javascript
    // Create async function that fetches user data from API
    // Handle loading and error states
    // Return { data, loading, error }
  2. Use Copilot Chat slash commands
    /explain [for understanding]
    /fix [for debugging]
    /tests [for test generation]
  3. Select code + open inline chat (Ctrl+I)
    [Select code] → Ctrl+I → "Convert to TypeScript"
  4. Use @workspace for project-wide questions
    @workspace how is authentication handled in this project?

ChatGPT / Claude Prompts

Best practices for standalone AI chats:

  1. Provide full context (they can’t see your files)
    I'm building a React app with TypeScript and Tailwind.
    Here's my current UserProfile component:
    [Paste code]
    I want to add...
  2. Ask for explanations with code
    Explain how this works and provide a refactored version:
    [Paste code]
  3. Request step-by-step instructions
    Step-by-step guide to add JWT authentication to my Express API
  4. Ask for comparisons
    Compare these two approaches and recommend the best one:
    Approach A:[Code]
    Approach B:[Code]

Advanced Prompting Techniques

Technique 1: Constrained Generation

Force the AI to follow specific rules:

Create a React component following these strict rules:
1. Maximum 50 lines of code
2. No external dependencies beyond React
3. Must be a pure functional component (no hooks)
4. All styling inline (no CSS files)
5. Fully accessible (ARIA labels, keyboard navigation)

Technique 2: Example-Driven Prompts

Show the AI exactly what you want:

I have this function:
function fetchUser(id) {
  return fetch(`/api/users/${id}`).then(res => res.json());
}

Create similar functions for:
- fetchPost(id)
- fetchComments(postId)
- fetchUserPosts(userId)

Same structure, different endpoints.
Code language: JavaScript (javascript)

Technique 3: Incremental Refinement

Build complexity gradually:

Round 1: "Create a basic button component"
[Review]

Round 2: "Add variants: primary, secondary, danger"
[Review]

Round 3: "Add loading state with spinner"
[Review]

Round 4: "Add size prop: small, medium, large"
[Final version]
Code language: PHP (php)

Technique 4: Negative Constraints

Tell the AI what NOT to do:

Create a user signup form.

Do NOT:
- Use any external validation libraries
- Add password strength indicator (will add later)
- Include social login buttons
- Add reCAPTCHA (not needed yet)
- Use class components (functional only)
Code language: PHP (php)

This prevents the AI from over-engineering.

Technique 5: Role-Based Prompting

Frame the request from a specific role:

As a senior backend engineer reviewing this code, identify:
- Security vulnerabilities
- Performance bottlenecks
- Error handling issues
- Best practice violations

[Paste code]
Code language: JavaScript (javascript)

Technique 6: Test-Driven Prompting

Start with tests, generate implementation:

Here are the test cases my function needs to pass:

test('adds two numbers', () => {
  expect(calculate('2 + 3')).toBe(5);
});

test('handles multiplication', () => {
  expect(calculate('4 * 5')).toBe(20);
});

test('respects order of operations', () => {
  expect(calculate('2 + 3 * 4')).toBe(14);
});

Create the calculate() function that passes these tests.
Code language: PHP (php)

Common Prompt Anti-Patterns

Anti-Pattern #1: The Vague Ask

Bad:

Make this better
Code language: JavaScript (javascript)

Why it fails: “Better” is subjective. Better how? Performance? Readability? Features?

Fix:

Refactor this to improve readability:
- Extract magic numbers into named constants
- Add comments for complex logic
- Break long function into smaller helpers
Code language: JavaScript (javascript)

Anti-Pattern #2: The Context-Free Question

Bad:

Why doesn't this work?
[Pastes code with no error message]

Why it fails: AI doesn’t know what “work” means or what error you’re seeing.

Fix:

This login function returns undefined instead of a user object.

Code:
[Paste code]

Error in console:
Cannot read property 'id' of undefined

Expected: Should return user object from database

Anti-Pattern #3: The Kitchen Sink

Bad:

Create a complete e-commerce website with user auth, product catalog, shopping cart, payment processing, admin dashboard, email notifications, and analytics
Code language: JavaScript (javascript)

Why it fails: Way too broad. AI will give you a high-level skeleton, not working code.

Fix: Break into 20 smaller prompts, one feature at a time.

Anti-Pattern #4: The Assumed Context

Bad:

Add the user profile feature

Why it fails: AI doesn’t know what “the user profile feature” means in your specific app.

Fix:

Add a user profile page (src/pages/Profile.tsx):
- Shows user's name, email, avatar
- "Edit Profile" button navigates to /profile/edit
- Uses UserCard component from src/components/
- Fetches data from GET /api/users/:id
- Matches styling of Dashboard.tsx

Anti-Pattern #5: The No-Constraints Ask

Bad:

Create a form validation library

Why it fails: AI will create something generic that doesn’t fit your needs.

Fix:

Create a form validation library:
- React hooks-based (useForm, useField)
- Schema validation with Zod
- Built-in async validation support
- TypeScript with full type inference
- < 5KB bundle size
- No dependencies beyond Zod
Code language: JavaScript (javascript)

Debugging Bad Prompts

If you’re not getting good results, ask yourself:

Question 1: Is it specific enough?

Too vague: “Create a dashboard”
Specific: “Create a React dashboard with user stats cards, a recent activity table, and a chart showing signups over time”

Question 2: Did I provide context?

No context: “Fix this bug”
With context: “Fix authentication bug in src/auth.ts – JWT verification fails with ‘invalid signature’ error when token is valid”

Question 3: Did I mention technical requirements?

No tech: “Create an API endpoint”
With tech: “Create a POST endpoint in Express.js with Zod validation and Prisma database access”

Question 4: Did I reference existing patterns?

No reference: “Style this component”
With reference: “Style this component using Tailwind classes matching the design of Header.tsx”

Question 5: Did I set constraints?

No constraints: “Optimise this code”
With constraints: “Optimise this code for performance without changing the public API or adding dependencies”


Measuring Prompt Quality

The First-Try Success Rate

Track how often your prompts work on the first try:

  • < 30%: Your prompts need more specificity
  • 30-60%: Average (still room for improvement)
  • 60-80%: Good prompt engineering
  • > 80%: Excellent prompting skills

The Edit Distance

How much do you need to modify AI-generated code:

  • Major rewrites: Prompt was too vague
  • Minor tweaks: Good prompt, small AI mistakes
  • Copy-paste ready: Excellent prompt

Goal: Aim for minor tweaks or less.


Prompt Engineering Checklist

Before submitting a prompt, verify:

  • [ ] Specified the exact action (create, refactor, fix, etc.)
  • [ ] Provided relevant context (tech stack, file structure)
  • [ ] Listed specific requirements (features, behaviour)
  • [ ] Set constraints (what NOT to do)
  • [ ] Mentioned code style preferences
  • [ ] Referenced existing files as examples (if applicable)
  • [ ] Included error messages (if debugging)
  • [ ] Avoided vague words (“better,” “improve,” “optimise”)

Real-World Before/After Examples

Example 1: Creating a Component

Before (vague):

Create a login form

After (specific):

Create a React login form component (src/components/LoginForm.tsx):

Fields:
- Email input with validation (must be valid email)
- Password input with show/hide toggle (min 8 chars)

UI:
- Submit button (disabled during submission)
- Error message display (red text below form)
- "Forgot password?" link
- "Sign up" link

Behavior:
- On submit: Call loginUser(email, password) API function
- Show loading spinner in button during submission
- Display API error if login fails
- Navigate to /dashboard on success

Styling:
- Use Tailwind CSS
- Match the card style from SignupForm.tsx
- Mobile responsive (stack fields on small screens)

Validation:
- Use react-hook-form for validation
- Show errors below each field
Code language: JavaScript (javascript)

Result: First prompt generated production-ready code.

Example 2: Debugging

Before (context-free):

Fix the error in fetchData

After (full context):

Fix TypeError in src/api/fetchData.ts:

Error message:
TypeError: Cannot read properties of undefined (reading 'map')
at fetchData (fetchData.ts:23)

Current code:
async function fetchData(userId) {
  const response = await fetch(`/api/users/${userId}/posts`);
  const data = await response.json();
  return data.posts.map(post => ({ ...post, userId }));
}

Issue:
When API returns error (404), data structure is { error: "Not found" }, not { posts: [...] }, causing the error.

Expected behavior:
- Handle error responses gracefully
- Return empty array if posts don't exist
- Don't crash on API errors
Code language: PHP (php)

Result: AI immediately identified the issue and provided proper error handling.

Example 3: Refactoring

Before (vague improvement):

Make this code better
Code language: JavaScript (javascript)

After (specific improvements):

Refactor src/components/UserList.tsx for performance:

Current issues:
- Re-renders on every parent update (even when users prop unchanged)
- Sorts users array on every render (expensive for 1000+ users)
- Creates new event handler functions on every render

Target improvements:
- Wrap component in React.memo
- Use useMemo for sorted users array
- Use useCallback for event handlers
- Split large component into UserListItem subcomponent

Requirements:
- Don't change component API (same props)
- Maintain all existing functionality
- Add comments explaining optimizations
Code language: PHP (php)

Result: AI optimised the component with proper React patterns.


Language-Specific Prompt Tips

JavaScript / TypeScript

[Always specify:]
- Use TypeScript or JavaScript
- Module system (ESM, CommonJS)
- Target environment (Node, Browser, both)
- Async pattern (async/await, promises, callbacks)

Example:
"Create a TypeScript function using ESM exports and async/await"
Code language: JavaScript (javascript)

Python

[Always specify:]
- Python version (3.x)
- Type hints (yes/no)
- Docstring style (Google, NumPy, reStructuredText)
- Libraries to use

Example:
"Create Python 3.11 function with type hints and Google-style docstrings"
Code language: JavaScript (javascript)

React

[Always specify:]
- Functional or class component
- Hooks used
- State management (local, Context, Redux)
- Styling approach (CSS, Tailwind, styled-components)

Example:
"Create functional React component using hooks and Tailwind CSS"
Code language: CSS (css)

Backend APIs

[Always specify:]
- Framework (Express, FastAPI, etc.)
- Database (PostgreSQL, MongoDB, etc.)
- ORM/ODM (Prisma, Mongoose, etc.)
- Auth method (JWT, sessions, etc.)

Example:
"Create Express.js endpoint using Prisma for PostgreSQL with JWT auth"
Code language: JavaScript (javascript)

Next Steps

You now understand how to write effective prompts for AI coding tools. To improve further:

  1. Practice daily – Rewrite your prompts before submitting
  2. Analyse failures – When AI code is wrong, identify what your prompt missed
  3. Build a personal library – Save your best prompts for reuse
  4. Read related guides:
  5. How to Use Cursor AI – Cursor-specific techniques
  6. How to Use GitHub Copilot Chat – Copilot prompting
  7. Cursor Composer Mode Guide – Multi-file prompting

FAQ

Do I need to write long prompts every time?

No. For simple tasks, short prompts work fine: “Add error handling” or “Convert to async/await.” Save detailed prompts for complex features, new components, or when simple prompts fail.

How do I know if my prompt is too long?

If you’re writing more than 300 words, consider breaking into multiple prompts. Exception: Complex features genuinely need detailed specs. Use the 5-part framework to stay organized.

Can I reuse prompts across projects?

Yes! Build a personal prompt library. Replace project-specific details (file paths, component names) but keep the structure. Great prompts are templates you adapt.

What if the AI still gets it wrong?

Iterate. Don’t rewrite from scratch—refine: “Keep the same structure but change X to Y” or “Fix the error on line 23 where Z happens.” AI understands incremental feedback better than starting over.

Should I use different prompting styles for different AI tools?

Yes, slightly. Cursor benefits from file path mentions. Copilot works well with comment-style prompts. ChatGPT needs more context since it can’t see your code. But the core principles (specificity, context, constraints) apply everywhere.

How do I prompt for security-critical code?

Always add explicit security constraints:

"Sanitize all user inputs to prevent XSS"
"Use parameterized queries to prevent SQL injection"
"Validate and escape all outputs"
"Don't log sensitive data"
Code language: JSON / JSON with Comments (json)

Then manually review the generated code. Never trust AI blindly for auth, payments, or data validation.

Can prompting replace understanding code?

No. Prompt engineering amplifies your skills; it doesn’t replace them. You need to understand what you’re asking for, evaluate if the generated code is correct, and debug issues. AI is a powerful assistant, not a replacement for knowledge.


Related Articles:
How to Use Cursor AI: Complete Beginner’s Guide
Cursor Composer Mode: Advanced Multi-File Editing
How to Use GitHub Copilot Chat


Last updated: January 2025

Leave a Reply

Your email address will not be published. Required fields are marked *