Anatomy of a Good Prompt
Learning Outcomes
- Identify the 5 structural elements of an effective prompt
- Distinguish between good and bad prompts using concrete criteria
- Apply the specificity spectrum to calibrate prompt detail
- Recognise common structural patterns used in production prompts
- Rewrite vague prompts into structured, effective ones
Lesson Plan
| Segment | Duration | Topic |
|---|---|---|
| Intro | 3 min | Why structure matters for AI coding |
| Explain | 8 min | The 5 elements: role, context, task, constraints, format |
| Demo | 7 min | Good vs bad prompt comparison |
| Explain | 6 min | The specificity spectrum |
| Demo | 8 min | Common structural patterns |
| Practice | 6 min | Rewrite bad prompts as good ones |
| Wrap-up | 2 min | Key takeaways, preview next lesson |
Before You Begin
Pre-work:
- Have your AI coding tool open and ready (Claude Code, Cursor, or Codex)
- Think of a recent coding task where AI gave you an unhelpful answer
- Review the installation lesson if you haven't set up your prompts folder
Shopping List:
- Any AI coding tool (Claude Code, Cursor, or Codex CLI)
- A code project to test prompts against (any language)
- Your prompts folder from the installation lesson
Every effective prompt for code generation contains up to five structural elements. Not every prompt needs all five, but knowing them lets you add precision where it matters.
1. Role — Who the AI should be
You are a senior backend engineer specialising in REST API design.
2. Context — What the AI needs to know about the situation
I'm building a Node.js Express application that manages a library system.
We use PostgreSQL for storage and follow RESTful conventions.
3. Task — What you want the AI to do (the core instruction)
Create an endpoint that allows users to search books by title, author, or ISBN.
4. Constraints — Rules the output must follow
Use parameterised queries to prevent SQL injection.
Return paginated results (default 20 per page).
Handle the case where no results are found with a 200 and empty array, not a 404.
5. Format — How the output should be structured
Return the code as a single file with JSDoc comments on the handler function.
Include a curl example showing how to call the endpoint.
Here's what all five look like together:
You are a senior backend engineer specialising in REST API design.
I'm building a Node.js Express application that manages a library system.
We use PostgreSQL for storage and follow RESTful conventions.
Create an endpoint that allows users to search books by title, author, or ISBN.
Constraints:
- Use parameterised queries to prevent SQL injection
- Return paginated results (default 20 per page)
- Handle no results with a 200 and empty array, not a 404
Return the code as a single file with JSDoc comments on the handler function.
Include a curl example showing how to call the endpoint.
Let's compare prompts that produce poor results with structured versions that get it right.
Example 1: Creating a function
Bad:
Write a validation function
Good:
Write a TypeScript function called validateEmail that:
- Takes a string input
- Returns { valid: boolean; error?: string }
- Checks for @ symbol, valid domain, and no spaces
- Does NOT use regex — use string parsing methods instead
Why the good version works: it names the function, specifies the language, defines the return type, lists the validation rules, and adds a constraint about the approach.
Example 2: Refactoring code
Bad:
Make this code better
Good:
Refactor this function to improve readability:
- Extract the nested if-else into early returns
- Name the magic numbers (30, 7, 365) as constants
- Split the date formatting into a separate helper function
- Keep the same input/output contract
[paste the function here]
Example 3: Debugging
Bad:
This doesn't work, fix it
Good:
This Express middleware returns a 500 error when the user object is null.
Expected behaviour: return a 401 with { error: "Unauthorized" }.
The function is called after the auth token is validated but before
the user is fetched from the database.
[paste the middleware code]
Fix the null handling and add appropriate error responses.
The pattern: Good prompts are specific about the WHAT (task), the HOW (constraints), and the SHAPE (format). Bad prompts leave all three to guesswork.
Not every prompt needs maximum detail. Think of specificity as a dial you adjust:
Low specificity ←————————————————→ High specificity
"Add tests" "Add a unit test for calculateTax using Jest,
testing 0% bracket, middle bracket, and
top bracket, with edge case for negative input.
Mock the taxRateService dependency.
Follow AAA pattern (Arrange-Act-Assert)."
When to use LOW specificity:
- Exploratory work ("Show me three approaches to caching here")
- Simple, unambiguous tasks ("Add TypeScript types to this file")
- When you want creative solutions
- Brainstorming ("What patterns could simplify this module?")
When to use HIGH specificity:
- Production code that must follow conventions
- Complex logic with specific business rules
- When you've already tried low specificity and got poor results
- Security-sensitive or performance-critical code
The iterative approach:
Start at medium specificity. If the result is close but not right, add constraints. If the result is too narrow, remove them.
Attempt 1 (medium): "Write a caching layer for our API responses"
Result: Works but uses in-memory cache
Attempt 2 (higher): "Write a caching layer for our API responses using Redis,
with a TTL of 5 minutes for list endpoints and 1 hour for individual resources.
Include cache invalidation when a resource is updated via PUT or DELETE."
Result: Exactly what was needed
Over time, certain prompt structures have emerged as reliably effective for code generation. Here are four patterns you can use as templates:
Pattern 1: The Specification
Best for: new features, API endpoints, components
## What
[One sentence describing what to build]
## Requirements
- [Requirement 1]
- [Requirement 2]
- [Requirement 3]
## Technical Details
- Language/Framework: [X]
- Must integrate with: [existing systems]
- Error handling: [approach]
## Example Usage
[Show how the finished code would be called]
Pattern 2: The Transformation
Best for: refactoring, migration, format conversion
Transform [this code/file/pattern] from [current state] to [desired state].
Rules:
- Keep [X] unchanged
- Change [Y] to [Z]
- Remove [A]
- Add [B]
Here is the code:
[paste code]
Pattern 3: The Fix
Best for: debugging, error resolution
Bug: [describe the observable symptom]
Expected: [what should happen]
Actual: [what currently happens]
Context: [relevant system info]
[paste relevant code]
Fix the issue. Explain what caused it.
Pattern 4: The Explanation + Implementation
Best for: learning new APIs, understanding then building
I need to [goal]. I'm using [tech stack].
First, explain the approach you'd recommend and why.
Then implement it with:
- [constraint 1]
- [constraint 2]
- Comments explaining non-obvious decisions
Each pattern addresses a different type of work, but all share the same DNA: clear task, relevant context, and explicit constraints.
Let's practice transforming vague prompts into structured ones. For each bad prompt below, try rewriting it before reading the suggested improvement.
Exercise 1:
Bad: Make a login form
Improved:
Create a React login form component with:
- Email and password fields with labels
- Client-side validation (email format, password min 8 chars)
- A submit button that's disabled until both fields are valid
- Error message display below the form
- Accessible: proper aria labels, focus management on error
- Use controlled components (useState for form state)
- Call onSubmit(email, password) prop when the form is submitted
- Do not handle the API call — just pass values to the parent
Exercise 2:
Bad: Write some tests
Improved:
Write Jest unit tests for the attached UserService class. Cover:
1. createUser — happy path (valid input returns user object)
2. createUser — duplicate email throws ConflictError
3. getUser — returns null for non-existent ID
4. updateUser — partial update merges with existing fields
5. deleteUser — soft deletes (sets deletedAt, doesn't remove row)
Mock the database layer (UserRepository). Use describe/it blocks.
Each test should follow Arrange-Act-Assert structure.
Exercise 3:
Bad: This is slow, fix it
Improved:
This database query takes 3+ seconds for tables with over 10k rows.
It's called on every page load of the dashboard.
[paste query]
Optimise it for read performance. Consider:
- Adding appropriate indexes
- Restructuring JOINs if possible
- Whether pagination or a materialised view would help
- Keeping the same result format
Show the optimised query and explain what changed.
Your turn: Take that AI interaction you thought of in the pre-work — the one where AI gave an unhelpful answer. Rewrite your original prompt using the 5-element structure. Run it again. Notice the difference.
Questions & Answers
Key Takeaways
- Every prompt has up to 5 elements — role, context, task, constraints, and format. Use as many as the complexity demands.
- Good prompts define what "correct" looks like — don't leave success criteria to guesswork.
- Specificity is a dial, not a switch — start at medium, adjust based on results.
- Use patterns as templates — the Specification, Transformation, Fix, and Explanation patterns cover most coding tasks.
- Practice rewrites — taking a bad prompt and making it good is the fastest way to build the skill.
Next Steps: In Lesson 2 — Role & Context Setting, we'll dive deep into the first two elements and learn how setting the right role dramatically changes the quality of generated code.