Prompt Patterns Every Developer Should Know
What Are Prompt Patterns?
A prompt pattern is a template for asking AI questions in a way that gets better answers. Developers have specific needs: we need working code, not essays. This tutorial shows patterns that work for common developer tasks.
Pattern 1: The Specification Pattern
Use this when you want AI to build something new from scratch.
Structure
Build a [component/function/system] that:
- Does [specific behavior]
- Handles [edge cases]
- Should work with [existing code/framework]
- Requirements: [constraints, performance goals, etc]
Example
Build a React hook that:
- Fetches data from an API endpoint
- Retries up to 3 times on failure
- Returns loading, error, and data states
- Cancels requests if the component unmounts
- Works with the fetch API, not axios
Requirements: TypeScript, zero external dependencies beyond React
The AI now has clear constraints. It won't suggest a library you don't want. It knows exactly what states to handle.
When to Use
Use the specification pattern for:
- New features or components
- Refactoring to a different approach
- Building something you're not sure how to implement
Pattern 2: The Debugging Pattern
When your code breaks, give the AI what it needs to help you.
Structure
I'm getting this error:
[full error message and stack trace]
Context:
- This happens when [what triggers it]
- Related code: [paste relevant function or module]
- Expected behavior: [what should happen]
Environment: [Node 18 / Python 3.11 / etc]
Example
I'm getting this error:
TypeError: Cannot read property 'map' of undefined
at getUserList (src/services/userService.ts:24:15)
at processRequest (src/api/handlers.ts:41:8)
Context:
This happens when I call the /users API endpoint.
Related code:
const getUserList = async (filters) => {
const response = await fetch('/api/users')
const data = await response.json()
return data.users.map(u => u.name) // Line 24
}
Expected behavior: Should return an array of user names.
Environment: Node 18, using fetch API
Now the AI sees the full picture. It can spot that response.json() might not include a users property, or the API is returning null.
When to Use
Use the debugging pattern for:
- Runtime errors
- Logic bugs
- Unexpected behavior
- Performance issues
Pattern 3: The Refactor Pattern
When you want to improve existing code.
Structure
Refactor this [function/component/module] to:
- [specific improvement, e.g., improve readability]
- [another goal, e.g., add TypeScript types]
- [constraint, e.g., keep the same API]
Current code:
[paste code]
Context: [why you want to change it, what's bothering you]
Example
Refactor this function to:
- Use async/await instead of promises
- Add proper error handling
- Keep the same function signature and behavior
Current code:
function fetchUserData(userId) {
return fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => processUser(data))
.catch(err => console.log(err))
}
Context: I'm migrating the codebase to async/await and need to add proper error handling instead of silent logging.
The AI refactors while respecting your constraints.
When to Use
Use the refactor pattern for:
- Modernizing code (callbacks to async/await)
- Improving readability
- Adding types (JavaScript to TypeScript)
- Fixing code smell
Pattern 4: The Review Pattern
When you want feedback on code you wrote.
Structure
Review this [function/component/module] against these criteria:
- [criterion 1, e.g., performance]
- [criterion 2, e.g., readability]
- [criterion 3, e.g., security]
Code:
[paste code]
Context: [what this does, who uses it, any constraints]
Example
Review this authentication function against these criteria:
- Security: Are there any vulnerabilities?
- Performance: Is there unnecessary overhead?
- Readability: Is the logic clear?
Code:
const authenticate = (username, password) => {
const user = findUser(username)
if (user && user.password === hashPassword(password)) {
return createToken(user.id)
}
return null
}
Context: This handles login in a Node.js API. It's called on every request to /login.
The AI checks for SQL injection risks, password comparison timing attacks, missing validation, and suggests improvements.
When to Use
Use the review pattern for:
- Security-sensitive code (auth, payments, data access)
- Performance-critical code (loops, API calls, database queries)
- Code you're unsure about
- Before submitting pull requests
Pattern 5: The Test Generation Pattern
When you need tests but aren't sure what to cover.
Structure
Generate tests for this [function/component] with [testing framework].
Cover these scenarios:
- [happy path: normal case]
- [edge case 1]
- [edge case 2]
- [error case]
Code:
[paste code]
Target coverage: [line coverage percentage if you care]
Example
Generate tests for this function with Jest.
Cover these scenarios:
- Valid input returns correct output
- Handles null/undefined input
- Throws on invalid type
- Works with edge case values (0, empty string, etc)
Code:
const calculateDiscount = (price, percentage) => {
if (percentage < 0 || percentage > 100) throw new Error('Invalid')
return price * (1 - percentage / 100)
}
Target coverage: 100% line coverage
The AI writes comprehensive tests covering all the cases you mentioned.
When to Use
Use the test generation pattern for:
- Writing tests from scratch
- Increasing test coverage
- Learning what good tests look like
- Adding tests to untested code
Pattern 6: The Documentation Pattern
When you need to document code for other developers (or future you).
Structure
Write [type of documentation] for this code.
Audience: [who will read this]
Include: [what should be explained]
Code:
[paste code]
Context: [why this exists, any non-obvious design choices]
Example
Write a JSDoc comment for this function.
Audience: Junior developers using this API
Include: What it does, parameters, return value, example usage
Code:
const retryWithBackoff = (fn, maxRetries = 3, baseDelay = 1000) => {
return async (...args) => {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn(...args)
} catch (err) {
if (i === maxRetries - 1) throw err
await new Promise(r => setTimeout(r, baseDelay * Math.pow(2, i)))
}
}
}
}
Context: This is a utility used in our API client to handle transient failures gracefully.
The AI writes clear documentation that explains not just what, but why and how to use it.
When to Use
Use the documentation pattern for:
- Public APIs or shared libraries
- Complex functions with non-obvious behavior
- Onboarding new team members
- Before publishing code
Combining Patterns
Often you'll use multiple patterns in a task. Example workflow:
- Specification: "Build a caching layer for our API"
- Refactor: "The AI suggested code, now refactor it to be more readable"
- Review: "Review it for thread safety and performance"
- Test Generation: "Write tests to cover the main scenarios"
- Documentation: "Write a README explaining how to use it"
Each pattern layers on the previous one, and AI maintains context throughout.
Tips for Better Results
- Be specific: "Fix my code" gets worse results than "This function should handle null input without crashing. Here's the error."
- Show your work: Pasting the actual code (not a description) is crucial
- Provide context: Explain why you're doing something, not just what you want
- State constraints: "Use only stdlib" or "Must be under 100ms" matters
- Give examples: Show what success looks like
Practice
Take a function from your current project and try:
- The debugging pattern (ask AI to spot issues)
- The review pattern (ask for security/performance feedback)
- The test generation pattern (ask for comprehensive tests)
Notice how each pattern gets better results than a vague "help me with this code" question.
Discussion
Sign in to comment. Your account must be at least 1 day old.