Prompt Engineering for Developers
Prompt engineering is the practice of crafting effective instructions for AI coding assistants to generate high-quality code, solve problems, and accelerate development workflows. As AI tools become integral to software development, mastering prompt engineering is essential for maximizing productivity while maintaining code quality.
Understanding AI Coding Assistants
Modern AI coding assistants leverage Large Language Models (LLMs) trained on vast codebases. Understanding their capabilities and limitations helps you work with them effectively.
Common AI Tools
- Cursor AI: IDE with integrated AI chat and inline editing capabilities
- GitHub Copilot: Inline code suggestions and chat interface
- Claude Code: Conversational AI for complex reasoning and code analysis
- Amazon CodeWhisperer: AWS-focused code suggestions
- Tabnine: Privacy-focused AI completions
How LLMs Process Code
AI models have limited context windows (typically 4K-200K tokens). Every prompt, file content, and conversation history consumes tokens. Efficient context management is crucial for quality results.
Core Principles of Effective Prompting
1. Be Specific and Explicit
Vague prompts lead to generic, often incorrect code. Provide clear requirements, constraints, and expected behavior.
Bad Prompt:
Create a function to process data
Good Prompt:
Create a TypeScript function that:
- Accepts an array of user objects with id, name, and email fields
- Filters out users without valid email addresses (must contain @ and .)
- Returns a sorted array by name (case-insensitive)
- Handles empty arrays gracefully
- Includes TypeScript types and JSDoc comments
2. Provide Context
Include relevant information about your architecture, coding standards, and existing patterns.
Example with Context:
We use React with TypeScript and follow these patterns:
- Functional components with hooks
- Custom hooks for data fetching (useQuery pattern)
- Error boundaries for error handling
- Zod for validation
Create a user profile component that fetches and displays user data with loading and error states.
3. Specify Quality Requirements
Explicitly request testing, error handling, documentation, or performance considerations.
Implement a retry mechanism for HTTP requests that:
- Uses exponential backoff (starting at 100ms, max 5 attempts)
- Only retries on network errors and 5xx status codes
- Includes comprehensive unit tests with mocked fetch
- Has proper TypeScript types
- Logs retry attempts for debugging
Prompting Patterns for Common Tasks
Code Generation Pattern
Use a structured template for generating new code:
Task: [What you want to build]
Requirements:
- [Functional requirement 1]
- [Functional requirement 2]
- [Non-functional requirement]
Constraints:
- [Technology/library constraint]
- [Performance constraint]
- [Compatibility constraint]
Context:
- [Existing architecture details]
- [Coding standards]
- [Related code patterns]
Please include:
- Error handling
- Input validation
- Unit tests
- Documentation
Debugging Pattern
Structure debugging requests for faster resolution:
Problem: [Clear description of the issue]
Expected behavior: [What should happen]
Actual behavior: [What actually happens]
Code:
[Relevant code snippet]
Error message:
[Full error stack trace]
Environment:
- [Language/framework version]
- [Relevant dependencies]
What I've tried:
- [Attempted solution 1]
- [Attempted solution 2]
Example:
Problem: API endpoint returns 500 error when creating a user
Expected: Should return 201 with user object
Actual: Returns 500 with "Cannot read property 'id' of undefined"
Code:
```typescript
async function createUser(req: Request, res: Response) {
const { name, email } = req.body;
const user = await db.users.create({ name, email });
return res.status(201).json({ id: user.id, name, email });
}
```
Error: TypeError: Cannot read property 'id' of undefined at createUser
Environment: Node.js 18, Express 4.18, PostgreSQL
What I've tried:
- Verified database connection works
- Checked that create method exists
Refactoring Pattern
Guide AI through thoughtful refactoring:
Current code:
[Code to refactor]
Issues with current code:
- [Problem 1: e.g., poor readability]
- [Problem 2: e.g., code duplication]
- [Problem 3: e.g., performance concerns]
Refactoring goals:
- [Goal 1: e.g., extract reusable functions]
- [Goal 2: e.g., improve error handling]
- [Goal 3: e.g., add type safety]
Please:
- Explain the changes you make
- Maintain backward compatibility
- Keep existing tests passing
Code Review Pattern
Get comprehensive code reviews:
Please review this code for:
1. Security vulnerabilities (SQL injection, XSS, etc.)
2. Performance issues (N+1 queries, memory leaks)
3. Error handling gaps
4. Code style and best practices
5. Missing edge cases in tests
Code:
[Code to review]
Context:
- This is a [description of component/feature]
- It handles [critical functionality]
- Performance requirements: [requirements]
Advanced Prompting Techniques
Chain-of-Thought Prompting
Break complex tasks into steps for better reasoning:
I need to implement a rate limiting middleware for our API.
Please approach this step-by-step:
1. First, analyze the requirements and suggest an appropriate algorithm
2. Then, design the data structure for tracking requests
3. Next, implement the core rate limiting logic
4. Finally, add comprehensive tests for edge cases
Requirements:
- Support per-user rate limits (100 requests/minute)
- Use Redis for distributed rate limiting
- Return appropriate HTTP 429 responses
- Include Retry-After header
Few-Shot Learning
Provide examples of your desired code style:
Here's how we structure our API handlers:
Example 1:
```typescript
export const getUser = asyncHandler(async (req, res) => {
const userId = parseInt(req.params.id);
const user = await userService.findById(userId);
if (!user) {
throw new NotFoundError('User not found');
}
return res.json({ data: user });
});
```
Example 2:
```typescript
export const deletePost = asyncHandler(async (req, res) => {
const postId = parseInt(req.params.id);
await postService.delete(postId);
return res.status(204).send();
});
```
Now create a handler for updating a user's profile following the same pattern.