Prompt engineering sounds like marketing jargon, but it's a real skill that directly impacts your productivity with AI tools. This guide covers practical techniques specifically for software development tasks.
Why Prompting Matters for Developers#
The same AI model can produce vastly different outputs based on how you ask. Consider these two approaches to the same problem:
Approach A: Basic prompt
Write a function to validate emails
Result: Generic regex that misses edge cases
Approach B: Engineered prompt
Write an email validation function in TypeScript that:
- Validates format according to RFC 5322
- Checks for disposable email domains
- Returns a typed result: { valid: boolean, reason?: string }
- Include the common edge cases you're handling in comments
Use this signature: validateEmail(email: string): ValidationResult
Result: Production-ready validator with edge case handling
The Developer's Prompting Framework#
1. Specify the Technology Stack#
AI models know many languages and frameworks. Be explicit:
1Good:
2"Using TypeScript 5, React 18, and TanStack Query v5..."
3
4"In a Next.js 14 App Router application with Prisma ORM..."
5
6"For a Python 3.12 FastAPI backend with SQLAlchemy..."
7
8Bad:
9"In my web app..."
10"For my backend..."2. Define Input/Output Contracts#
Tell AI exactly what goes in and comes out:
1Create a function that:
2
3Input:
4- orders: Array of Order objects (id, userId, total, status, createdAt)
5- userId: string to filter by
6
7Output:
8- Object with:
9 - orders: filtered and sorted (newest first)
10 - stats: { count, totalSpent, averageOrder }
11
12Handle:
13- Empty orders array → return empty results with zero stats
14- No matching userId → same as empty
15- Invalid userId (null/undefined) → throw ValidationError3. Provide Constraints#
Constraints guide AI toward your requirements:
1Constraints:
2- Function must be pure (no side effects)
3- No external dependencies
4- Must complete in O(n) time
5- Memory usage proportional to input size
6- Compatible with ES20204. Reference Existing Patterns#
Show how your codebase does things:
1Follow our existing service pattern:
2
3```typescript
4// Our pattern
5export class UserService {
6 constructor(private db: Database, private cache: Cache) {}
7
8 async findById(id: string): Promise<User | null> {
9 const cached = await this.cache.get(`user:${id}`);
10 if (cached) return cached;
11
12 const user = await this.db.user.findUnique({ where: { id } });
13 if (user) await this.cache.set(`user:${id}`, user, 3600);
14
15 return user;
16 }
17}Now create an OrderService following this same pattern with methods:
- findById, findByUserId, create, updateStatus
## Task-Specific Prompting
### For Code Generation
```markdown
Template:
Create [what] in [language/framework] that [does what].
Context:
- [relevant codebase information]
- [existing patterns to follow]
Requirements:
- [functional requirements]
- [non-functional requirements]
Constraints:
- [technical constraints]
- [style constraints]
Example usage:
[how the code will be called]
Example:
1Create a rate limiting middleware in Express.js that limits
2requests per IP address.
3
4Context:
5- We use Redis for distributed state
6- Other middleware follows: (req, res, next) => pattern
7- We have a logger at req.logger
8
9Requirements:
10- 100 requests per minute per IP
11- Return 429 with Retry-After header when exceeded
12- Bypass for whitelisted IPs from config
13- Log rate limit events
14
15Constraints:
16- Must work across multiple server instances
17- Should add minimal latency (<5ms)
18- Redis operations must be atomic
19
20Example usage:
21app.use('/api', rateLimiter({ limit: 100, window: 60 }));For Debugging#
1Template:
2I'm seeing [symptom] when [action].
3
4Environment:
5- [versions, configuration]
6
7What I've tried:
8- [debugging steps taken]
9
10Relevant code:
11[paste code]
12
13Logs/Errors:
14[paste errors]
15
16Questions:
171. What could cause this?
182. How can I verify?
193. What's the fix?Example:
1I'm seeing "Cannot read property 'id' of undefined" when
2loading the user profile page.
3
4Environment:
5- Next.js 14.1.0
6- React 18.2
7- Node 20
8
9What I've tried:
10- Verified user exists in database
11- Added console.log - user object is populated
12- Error happens only on first load, not on navigation
13
14Relevant code:
15```typescript
16export default async function ProfilePage() {
17 const user = await getCurrentUser();
18 console.log('User:', user); // Shows: { id: '123', name: 'Test' }
19
20 return <Profile userId={user.id} />; // Error here
21}Error: TypeError: Cannot read property 'id' of undefined at ProfilePage (app/profile/page.tsx:5:35)
Questions:
- Why does console.log show user but accessing .id fails?
- Is this a hydration issue?
- How should I fix this?
### For Code Review
```markdown
Template:
Review this [type of code] for [specific concerns].
Context:
- [what the code does]
- [why it was written this way]
Code:
[paste code]
Focus on:
- [specific aspects to review]
Our standards:
- [relevant coding standards]
Example:
1Review this authentication middleware for security issues.
2
3Context:
4- Handles JWT verification for API routes
5- Tokens are issued by our auth service
6- User roles are embedded in the token
7
8Code:
9```typescript
10export function authMiddleware(req, res, next) {
11 const token = req.headers.authorization?.split(' ')[1];
12
13 if (!token) {
14 return res.status(401).json({ error: 'No token' });
15 }
16
17 try {
18 const decoded = jwt.verify(token, process.env.JWT_SECRET);
19 req.user = decoded;
20 next();
21 } catch (err) {
22 return res.status(401).json({ error: 'Invalid token' });
23 }
24}Focus on:
- Token validation completeness
- Error handling security
- Header parsing robustness
Our standards:
- Never expose internal errors to clients
- Log security events
- Use typed errors
### For Refactoring
```markdown
Template:
Refactor this code to [achieve goal] while [maintaining constraint].
Current code:
[paste code]
Problems with current code:
- [issue 1]
- [issue 2]
Goals:
- [what refactored code should achieve]
Keep unchanged:
- [what must remain the same]
- [API contracts, etc.]
For Test Generation#
1Template:
2Generate tests for [code] covering [scope].
3
4Code under test:
5[paste code]
6
7Test framework: [framework]
8Testing style: [describe your patterns]
9
10Cover:
11- [specific scenarios]
12- [edge cases]
13
14Mocking approach:
15- [what to mock and how]Advanced Techniques#
Chain of Thought for Complex Problems#
For complex tasks, ask AI to think step by step:
1Design a caching strategy for our e-commerce product catalog.
2
3Think through this step by step:
4
51. First, identify what data needs caching
62. Then, determine appropriate TTLs for each type
73. Consider cache invalidation triggers
84. Design the cache key structure
95. Plan for cache stampede prevention
106. Finally, write the implementation
11
12Show your reasoning at each step.Few-Shot Learning#
Provide examples of input-output pairs:
1Convert these plain English descriptions to SQL queries.
2
3Examples:
4"all users created this month" →
5SELECT * FROM users WHERE created_at >= DATE_TRUNC('month', CURRENT_DATE)
6
7"top 10 products by revenue" →
8SELECT p.*, SUM(oi.quantity * oi.price) as revenue
9FROM products p
10JOIN order_items oi ON p.id = oi.product_id
11GROUP BY p.id
12ORDER BY revenue DESC
13LIMIT 10
14
15"users who haven't ordered in 90 days" →
16SELECT u.* FROM users u
17WHERE NOT EXISTS (
18 SELECT 1 FROM orders o
19 WHERE o.user_id = u.id
20 AND o.created_at > CURRENT_DATE - INTERVAL '90 days'
21)
22
23Now convert:
24"average order value by customer segment"Role Assignment#
Assign a specific perspective:
1As a senior security engineer reviewing code for vulnerabilities,
2examine this authentication flow:
3
4[code]
5
6Identify:
71. Security vulnerabilities (with severity ratings)
82. Missing security controls
93. Recommendations for hardeningOther useful roles:
- "As a performance engineer..." for optimization
- "As a junior developer seeing this for the first time..." for readability
- "As a database administrator..." for query optimization
- "As someone maintaining this code in 2 years..." for maintainability
Constraint Forcing#
Sometimes you need to force specific approaches:
1Implement user search WITHOUT using:
2- Full-text search databases
3- External services
4- More than O(n) complexity per search
5
6We have: PostgreSQL with standard indexes
7
8Must support:
9- Name search (partial match)
10- Email search (exact)
11- Role filter
12- PaginationBuilding a Prompt Template Library#
Create reusable templates for common tasks:
1// prompts/templates.ts
2
3export const templates = {
4 apiEndpoint: `
5Create a Next.js API route at {path} that {description}.
6
7Tech: Next.js 14, TypeScript, Prisma, Zod
8Auth: Clerk - use auth() helper
9Validation: Zod schemas
10Errors: Use ApiError class
11
12Methods needed: {methods}
13Request body: {body}
14Response shape: {response}
15
16Follow existing pattern in: {existingExample}
17`,
18
19 reactComponent: `
20Create a React component called {name} that {description}.
21
22Tech: React 18, TypeScript, Tailwind CSS
23State: {stateApproach}
24Props: {props}
25
26Requirements:
27- {requirements}
28
29Styling:
30- Follow existing Tailwind patterns
31- Mobile-first responsive design
32- Support dark mode via dark: prefix
33`,
34
35 unitTest: `
36Write unit tests for {functionName} using {testFramework}.
37
38Function code:
39{code}
40
41Test cases needed:
42- Happy path: {happyPath}
43- Edge cases: {edgeCases}
44- Error cases: {errorCases}
45
46Mocking approach: {mocking}
47`,
48};Iterative Refinement#
Rarely is the first output perfect. Use refinement prompts:
Narrowing#
That's close, but modify it to:
- Use dependency injection instead of direct imports
- Add JSDoc comments
- Extract the validation into a separate function
Expanding#
Good start. Now also add:
- Support for bulk operations
- Retry logic for transient failures
- Metrics collection for monitoring
Correcting#
The caching logic has a race condition. Fix it by:
- Using atomic Redis operations
- Implementing proper locking
- Adding a fallback for lock timeout
Common Pitfalls#
Pitfall 1: Over-reliance on Single Prompts#
Don't expect one prompt to produce perfect results. Plan for 2-3 iterations.
Pitfall 2: Under-specifying Types#
❌ "Return the user data"
✅ "Return a User object with { id: string, email: string, name: string | null }"
Pitfall 3: Ignoring Edge Cases#
Always ask about edge cases:
Also consider and handle:
- Empty inputs
- Null/undefined values
- Very large inputs
- Concurrent access
- Network failures
Pitfall 4: Not Validating Output#
AI makes mistakes. Always:
- Read the generated code
- Test it
- Check for security issues
- Verify it matches your patterns
Conclusion#
Prompt engineering for developers boils down to:
- Be specific about your tech stack
- Define clear contracts
- Show examples from your codebase
- Iterate and refine
- Always validate output
These aren't magic techniques—they're communication skills applied to AI interaction.
Bootspring agents understand your codebase context automatically, reducing the need for detailed prompts. Focus on what you want, not how to explain your setup.