> "The difference between a novice and an expert vibe coder isn't what tools they use — it's how they communicate with those tools." — Adapted from a common software engineering proverb
In This Chapter
- Learning Objectives
- Prerequisites
- Introduction
- 12.1 Chain-of-Thought Prompting for Complex Logic
- 12.2 Few-Shot Prompting: Teaching by Example
- Example 2: Phone number validation
- Example:
- Now convert these functions:
- Functional Constraints
- Performance Constraints
- Compatibility Constraints
- Code Quality Constraints
- Error Handling Constraints
- Hard Constraints (must be satisfied)
- Soft Constraints (preferred)
- Chain State (update after each step)
- Combining Techniques: A Decision Framework
- Putting It All Together: A Real-World Workflow
- Summary
Chapter 12: Advanced Prompting Techniques
"The difference between a novice and an expert vibe coder isn't what tools they use — it's how they communicate with those tools." — Adapted from a common software engineering proverb
Learning Objectives
After completing this chapter, you will be able to:
- Analyze the strengths and limitations of each advanced prompting technique and select the right one for a given coding task (Bloom's: Analyze)
- Apply chain-of-thought prompting to guide AI through multi-step algorithmic reasoning (Bloom's: Apply)
- Create few-shot prompts that teach AI your project's patterns and conventions through examples (Bloom's: Create)
- Evaluate when role-based prompting genuinely improves output versus when it adds unnecessary complexity (Bloom's: Evaluate)
- Apply meta-prompting to iteratively improve your own prompts using AI feedback (Bloom's: Apply)
- Create decomposition strategies that break complex software tasks into manageable sub-prompts (Bloom's: Create)
- Apply constraint satisfaction prompting to enforce specific requirements in generated code (Bloom's: Apply)
- Evaluate multiple AI-generated approaches using comparative prompting techniques (Bloom's: Evaluate)
- Apply Socratic prompting to deepen your understanding of AI-generated solutions (Bloom's: Apply)
- Create a personal prompt library organized by technique, domain, and effectiveness (Bloom's: Create)
Prerequisites
This chapter assumes you have completed:
- Chapter 8: Prompt Engineering Fundamentals — You understand prompt anatomy, clarity, specificity, and common anti-patterns.
- Chapter 9: Context Management — You can manage multi-turn conversations and strategic context placement.
- Chapter 11: Iterative Refinement — You know how to use feedback loops and follow-up prompts to improve AI output.
If you skipped those chapters, you will still be able to follow along, but some concepts build directly on that foundation.
Introduction
In Chapter 8, you learned the fundamentals of prompt engineering: how to write clear, specific, context-rich prompts that produce useful code. Those fundamentals will carry you far. But as your projects grow in complexity — as you move from generating isolated functions to designing entire systems — you need techniques that go deeper.
This chapter introduces ten advanced prompting techniques that expert vibe coders use daily. Each technique is a distinct mental model for communicating with AI, and each excels in different situations. Chain-of-thought prompting helps AI reason through complex algorithms. Few-shot prompting teaches AI your specific patterns. Role-based prompting unlocks specialized expertise. Meta-prompting turns AI into a prompt-improvement engine.
Think of these techniques as tools in a toolkit. A carpenter doesn't use a hammer for every task — they select the right tool for the job. By the end of this chapter, you will have ten well-understood tools and the judgment to know when each one applies.
12.1 Chain-of-Thought Prompting for Complex Logic
What It Is
Chain-of-thought (CoT) prompting is a technique where you explicitly ask the AI to reason step-by-step before producing code. Instead of jumping directly to a solution, the AI first articulates its reasoning process — identifying sub-problems, considering edge cases, evaluating trade-offs — and only then writes the implementation.
The technique was formalized in research by Wei et al. (2022), who demonstrated that large language models produce significantly more accurate results on complex reasoning tasks when prompted to show their work. For vibe coders, this translates directly into better code for problems that involve multi-step logic, algorithmic complexity, or non-obvious design decisions.
When to Use It
Chain-of-thought prompting is most valuable when:
- The problem involves multiple logical steps that must be connected correctly
- You need an algorithm with non-trivial complexity (sorting, graph traversal, dynamic programming)
- The task requires reasoning about edge cases before implementing
- You want the AI to evaluate trade-offs between different approaches
- The problem involves mathematical or logical reasoning that could go wrong silently
Intuition
Imagine asking someone to solve a Sudoku puzzle. If you say "solve this," they might stare at it and guess. But if you say "walk me through your reasoning as you solve each cell," they'll produce a more reliable solution because the explicit reasoning process catches errors that silent thinking might miss. Chain-of-thought prompting works the same way with AI.
How to Use It
The simplest form adds a reasoning preamble to your prompt:
Before writing any code, think through this problem step by step:
1. Identify the inputs and outputs
2. List the key constraints
3. Consider edge cases
4. Outline your algorithm in pseudocode
5. Then implement the solution
Problem: Write a function that finds the longest substring without
repeating characters in a given string.
Here is what the AI's response typically looks like:
**Step-by-step reasoning:**
1. **Inputs and outputs:** Input is a string, output is the length
of the longest substring with all unique characters.
2. **Key constraints:**
- Characters can be any Unicode character
- Empty string should return 0
- Single character string should return 1
3. **Edge cases:**
- Empty string: ""
- All same characters: "aaaa"
- All unique characters: "abcdef"
- Repeats at boundaries: "abcabc"
4. **Algorithm (sliding window approach):**
- Maintain a window [left, right]
- Use a set to track characters in current window
- Expand right pointer: if character not in set, add it
- If character already in set, shrink from left until it's removed
- Track maximum window size throughout
5. **Implementation:**
def length_of_longest_substring(s: str) -> int:
"""Find the length of the longest substring without repeating characters.
Uses a sliding window approach with O(n) time complexity.
Args:
s: The input string to analyze.
Returns:
The length of the longest substring with all unique characters.
"""
char_index: dict[str, int] = {}
max_length = 0
left = 0
for right, char in enumerate(s):
if char in char_index and char_index[char] >= left:
left = char_index[char] + 1
char_index[char] = right
max_length = max(max_length, right - left + 1)
return max_length
Notice how the step-by-step reasoning led to a clean sliding-window solution rather than a naive O(n^2) brute-force approach. The reasoning process naturally guided the AI toward the optimal algorithm.
Advanced Chain-of-Thought Patterns
The "Reason About Correctness" Pattern:
Implement a function to merge two sorted linked lists. Before coding:
1. Define your approach in plain English
2. Trace through an example: [1,3,5] and [2,4,6]
3. Identify what happens at each step
4. Consider: what if one list is empty? What if both are empty?
5. Write the code, then verify it against your example trace
The "Compare Before Committing" Pattern:
I need to implement a cache eviction strategy. Before choosing an approach:
1. List three possible eviction strategies (LRU, LFU, FIFO)
2. For each, describe the time complexity of get() and put() operations
3. Analyze which is best for my use case: a web server with
temporal locality in requests
4. Implement your recommended approach with clear comments
Common Pitfall
Avoid asking for chain-of-thought reasoning on simple tasks. If you need a basic getter/setter or a straightforward CRUD operation, CoT prompting adds unnecessary verbosity without improving output quality. Save it for problems where reasoning genuinely helps — typically anything involving algorithms, complex business logic, or architectural decisions.
Structured Chain-of-Thought Template
For consistently high-quality results, use this structured template:
## Task
[Describe the problem clearly]
## Before You Code
Please reason through the following:
### Problem Analysis
- What are the exact inputs and outputs?
- What are the constraints (time, space, domain)?
### Edge Cases
- List at least 5 edge cases to handle
### Algorithm Design
- Describe your approach in pseudocode
- What is the time complexity?
- What is the space complexity?
### Implementation
Now write the code, incorporating your analysis above.
### Verification
Trace through at least 2 test cases to verify correctness.
This template consistently produces more reliable code than a bare request, particularly for algorithmic problems. In Section 12.10, we will discuss how to save templates like this one in your personal prompt library.
12.2 Few-Shot Prompting: Teaching by Example
What It Is
Few-shot prompting provides the AI with concrete examples of the input-output pattern you want, then asks it to follow that pattern for a new case. Instead of describing what you want in abstract terms, you show the AI exactly what "good" looks like, and it generalizes from your examples.
The term comes from machine learning, where "few-shot learning" refers to a model learning from very few examples. In the prompting context, even two or three well-chosen examples can dramatically improve the consistency and accuracy of AI output.
When to Use It
Few-shot prompting excels when:
- You need output that follows a specific format or convention your project uses
- You want the AI to match your team's coding style precisely
- The task involves pattern transformation (converting one data format to another)
- You need consistent naming conventions across multiple generated items
- Abstract description alone is insufficient to communicate what you want
Real-World Application
A fintech development team used few-shot prompting to generate data validation functions for their API. They provided three examples of their validation pattern — including their specific error message format, logging conventions, and return type structure — and then asked the AI to generate validators for 20 additional fields. The result was code that looked like a human on the team had written every function, with zero style inconsistencies.
How to Use It
Here is a concrete example. Suppose your project uses a specific pattern for data validation:
I need you to generate validation functions following this exact pattern.
## Example 1: Email validation
```python
def validate_email(value: str, field_name: str = "email") -> ValidationResult:
"""Validate an email address format.
Args:
value: The email string to validate.
field_name: The field name for error messages.
Returns:
ValidationResult with is_valid and optional error message.
"""
if not value or not value.strip():
return ValidationResult(
is_valid=False,
error=f"{field_name} is required",
error_code="REQUIRED"
)
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, value):
return ValidationResult(
is_valid=False,
error=f"{field_name} must be a valid email address",
error_code="INVALID_FORMAT"
)
return ValidationResult(is_valid=True)
Example 2: Phone number validation
def validate_phone(value: str, field_name: str = "phone") -> ValidationResult:
"""Validate a US phone number format.
Args:
value: The phone number string to validate.
field_name: The field name for error messages.
Returns:
ValidationResult with is_valid and optional error message.
"""
if not value or not value.strip():
return ValidationResult(
is_valid=False,
error=f"{field_name} is required",
error_code="REQUIRED"
)
cleaned = re.sub(r'[\s\-\(\)]', '', value)
if not re.match(r'^\+?1?\d{10}$', cleaned):
return ValidationResult(
is_valid=False,
error=f"{field_name} must be a valid US phone number",
error_code="INVALID_FORMAT"
)
return ValidationResult(is_valid=True)
Now generate validation functions following the same pattern for: 1. A US zip code (5 digits or 5+4 format) 2. A date of birth (must be in the past, person must be at least 18) 3. A US Social Security Number (XXX-XX-XXXX format)
The AI will produce functions that match your exact structure: same parameter names, same `ValidationResult` return type, same error code conventions, same docstring format.
### Choosing Good Examples
The quality of your examples determines the quality of the output. Follow these guidelines:
**Show variety in your examples.** If all your examples validate string formats with regex, the AI might assume every validation uses regex. Include at least one example that uses a different approach (like date comparison or range checking) if your real tasks require that variety.
**Include the non-obvious conventions.** If your team always puts the empty-check first, or always uses specific error code formats, make sure your examples demonstrate these conventions explicitly. The AI picks up on structural patterns in your examples.
**Use two to five examples.** Research and practical experience both suggest that two to three examples are usually sufficient. Beyond five examples, you are spending context window space without proportional improvement. One example is often too few for the AI to distinguish pattern from coincidence.
> **Best Practice**
>
> When building few-shot prompts, select examples that span the range of complexity you expect. If you only show simple cases, the AI may not handle complex ones well. Include one straightforward example, one moderately complex example, and one edge-case-heavy example for best results.
### Few-Shot for Code Transformation
Few-shot prompting is particularly powerful for systematic code transformations. Here is an example of converting synchronous code to async:
Convert the following synchronous functions to async, following this pattern:
Example:
Before:
def get_user(user_id: int) -> User: response = requests.get(f"{API_URL}/users/{user_id}") response.raise_for_status() return User(**response.json())
After:
async def get_user(user_id: int) -> User: async with aiohttp.ClientSession() as session: async with session.get(f"{API_URL}/users/{user_id}") as response: response.raise_for_status() data = await response.json() return User(**data)
Now convert these functions:
[your functions here]
### Anti-Patterns in Few-Shot Prompting
**Inconsistent examples.** If your examples follow different patterns from each other, the AI gets confused about which pattern to follow. Make sure all examples demonstrate the same conventions.
**Over-specification.** If your examples are all nearly identical with only one small difference, the AI may copy too rigidly and miss the intent to generalize. Your examples should share structure but vary in content.
**Irrelevant complexity.** Keep examples focused on the pattern you are teaching. If an example includes complex business logic unrelated to the pattern, it wastes context and may distract the AI.
---
## 12.3 Role-Based Prompting: Assigning Expertise
### What It Is
Role-based prompting instructs the AI to adopt a specific professional persona before responding. By saying "Act as a senior Python architect" or "You are a security auditor reviewing this code," you activate relevant knowledge patterns and evaluation criteria that the AI associates with that role.
This technique works because large language models have been trained on vast amounts of text written by and about different professional roles. When you invoke a role, you effectively filter the AI's response through the lens of that role's priorities, vocabulary, and standards.
### When to Use It
Role-based prompting is most effective when:
- You need **domain-specific expertise** applied to a coding task
- You want code reviewed from a **particular perspective** (security, performance, accessibility)
- You need the AI to **maintain a consistent level of sophistication** throughout a conversation
- You want to **simulate a code review** from a specific type of reviewer
- The task requires **cross-domain knowledge** (e.g., a data engineer who also understands HIPAA compliance)
### Practical Role Prompts
**The Senior Architect:**
Act as a senior software architect with 15 years of experience in Python backend systems. You prioritize: - Clean separation of concerns - SOLID principles - Scalability to millions of users - Clear documentation and type hints - Testability in every design decision
Design a notification system that supports email, SMS, and push notifications. The system should be easily extensible to new notification channels.
This prompt typically produces code with proper abstract base classes, dependency injection, the strategy pattern, and comprehensive docstrings — because that is what a senior architect would produce.
**The Security Auditor:**
You are a security engineer conducting a code review. For the following code, identify: 1. Any security vulnerabilities (OWASP Top 10) 2. Input validation gaps 3. Authentication/authorization weaknesses 4. Data exposure risks 5. Injection possibilities
For each issue, provide: - Severity (Critical/High/Medium/Low) - The specific code location - A concrete fix with code
Code to review: [paste code here]
**The Performance Engineer:**
Act as a performance engineer who specializes in Python optimization. Review this code and: 1. Identify performance bottlenecks 2. Estimate big-O complexity for each function 3. Suggest optimizations with expected improvement 4. Note any memory concerns 5. Recommend profiling strategies to validate improvements
Prioritize optimizations by impact — focus on the changes that will make the biggest difference first.
[paste code here]
> **Advanced**
>
> You can combine multiple roles in a single prompt for nuanced analysis. For example: "Act as both a security engineer and a performance engineer. When these priorities conflict (e.g., input validation adds overhead), explain the trade-off and recommend the best balance." This produces remarkably thoughtful responses that consider multiple dimensions simultaneously.
### Role Stacking and Role Switching
**Role stacking** uses multiple roles in a single prompt:
Review this database access layer from three perspectives:
- As a DBA: Is the schema design normalized? Are queries efficient?
- As a security engineer: Are there SQL injection risks? Is sensitive data protected?
- As a maintenance engineer: Is the code readable? Well-documented? Easy to modify?
Provide separate sections for each perspective, then a unified recommendation.
**Role switching** changes roles across a conversation:
[First prompt] As a product manager, help me define the requirements for a user authentication system. What features do users expect?
[Second prompt] Now switch to a security architect. Given those requirements, design a system architecture that implements them securely.
[Third prompt] Now act as a Python developer implementing the security architect's design. Write the authentication module.
> **Common Pitfall**
>
> Do not use role-based prompting as a magic incantation. Saying "Act as the world's best programmer" does not produce materially better code than a well-structured prompt without a role. The value of role-based prompting comes from activating *specific* expertise and *specific* evaluation criteria. "Act as a senior Python developer who follows Google's Python style guide and prioritizes type safety" is far more useful than vague superlatives.
### When Roles Help and When They Do Not
Roles help most when they bring **specific, relevant constraints** to the task. A security auditor role activates a checklist of security concerns. A database architect role activates normalization principles and query optimization heuristics.
Roles help least when the task is **straightforward and well-defined**. If you need a function to sort a list of integers, no amount of role-prompting will improve the output over a clear, direct request.
A good heuristic: if the role brings knowledge or priorities that you would not have included in your prompt otherwise, it is worth using. If the role just restates what you already specified, it is unnecessary overhead.
---
## 12.4 Meta-Prompting: Prompts That Generate Prompts
### What It Is
Meta-prompting is the technique of using AI to improve your prompts. Instead of writing the final prompt yourself, you describe your goal to the AI and ask it to generate an optimized prompt, which you then use (possibly with modifications) to get your actual result.
This is one of the most powerful techniques in the advanced vibe coder's toolkit because it turns the AI into a prompt engineering assistant. The AI knows what makes prompts effective — it has been trained on vast amounts of text about prompt engineering — and it can apply that knowledge to your specific situation.
### When to Use It
Meta-prompting is most valuable when:
- You are tackling a **new type of task** and unsure how to prompt for it
- Your current prompts are producing **inconsistent or mediocre** results
- You want to **optimize prompts** for a task you do repeatedly
- You need to create prompts for **non-technical team members** to use
- You are building a **prompt library** (Section 12.10) and want high-quality templates
### The Basic Meta-Prompt
I need to write a prompt that will ask an AI coding assistant to generate a REST API endpoint. The endpoint should handle user registration including input validation, password hashing, email verification token generation, and database storage.
My current prompt is: "Write a user registration endpoint in Python."
This produces code that is too basic — it lacks validation, security best practices, and error handling.
Please write an improved prompt that will produce production-quality code. The prompt should: 1. Specify all the components I need 2. Include quality constraints (error handling, security, testing) 3. Define the expected input/output format 4. Reference specific libraries I want to use (FastAPI, SQLAlchemy, Pydantic)
The AI might respond with:
Here's an optimized prompt:
Generate a user registration endpoint using FastAPI with the following requirements:
Framework and Libraries: - FastAPI for the endpoint - Pydantic v2 for request/response models - SQLAlchemy 2.0 with async session for database operations - passlib with bcrypt for password hashing - Python's secrets module for email verification tokens
Endpoint Specification: - POST /api/v1/auth/register - Accept JSON body with: email, password, first_name, last_name
Validation Requirements: - Email: valid format, not already registered (409 Conflict if exists) - Password: minimum 8 characters, at least one uppercase, one lowercase, one digit, one special character - Names: 1-50 characters, alphabetic with spaces and hyphens only
Security Requirements: - Hash passwords with bcrypt (never store plaintext) - Generate a cryptographically secure email verification token - Rate limit: suggest middleware approach - Return 201 Created on success (do not echo the password back)
Response Format: - Success: {"user_id": "uuid", "email": "...", "message": "..."} - Validation Error: 422 with field-level error details - Conflict: 409 with descriptive message
Code Quality: - Full type hints on all functions and parameters - Docstrings in Google style - Proper exception handling with custom exception classes - Async/await throughout - Include example test cases using pytest and httpx
Notice how much more detailed and structured the generated prompt is. When you use this improved prompt, the resulting code will be significantly more complete and production-ready.
> **Real-World Application**
>
> A development team at a mid-size SaaS company used meta-prompting to create a library of 30 optimized prompts for their most common development tasks. New developers on the team could produce code matching their standards from day one, simply by using the team's curated prompt library. The prompts were stored in their repository alongside their code, version-controlled and reviewed just like source code.
### Iterative Meta-Prompting
You can iterate on meta-prompts just like you iterate on code:
[First meta-prompt] Write a prompt for generating database migration scripts for our PostgreSQL database using Alembic.
[Review the generated prompt, then follow up] The prompt you generated doesn't mention: - Our naming convention for migration files (YYYYMMDD_HHMM_description) - That we always need both upgrade() and downgrade() functions - That we use specific PostgreSQL types (JSONB, ARRAY) frequently - Our team's requirement for data migration scripts alongside schema migrations
Please revise the prompt to include these requirements.
[After second iteration] Good. Now also add instructions about: - How to handle migrations that need to backfill data - Our convention for adding indexes (always concurrent) - Test requirements for each migration
Each iteration produces a more refined prompt that captures more of your team's specific knowledge and conventions.
### The Meta-Prompt Template
Here is a general-purpose meta-prompt template:
I need you to write an optimized prompt for an AI coding assistant.
Goal: [What the prompt should accomplish]
Context: [What the developer knows, what project they're working on]
Current approach that isn't working: [Optional: what they've tried]
Quality bar: [What "good enough" looks like for this task]
Constraints the prompt must enforce: - [List specific constraints]
Output format the prompt should request: - [Describe desired output structure]
Please write a detailed, well-structured prompt that will consistently produce high-quality results for this task.
> **Best Practice**
>
> Save your best meta-generated prompts. Over time, you build a collection of battle-tested prompts that encode your team's standards and expectations. We cover this systematically in Section 12.10 on building your personal prompt library.
---
## 12.5 Decomposition Prompting: Breaking Down Complexity
### What It Is
Decomposition prompting breaks a large, complex task into smaller, manageable sub-tasks that you prompt for individually. Instead of asking the AI to build an entire feature in one shot, you identify the logical components, generate each one separately, and then integrate them.
This technique directly addresses one of the biggest limitations of AI code generation: the quality of output degrades as task complexity increases. A prompt that asks for a simple function produces excellent code. A prompt that asks for an entire application with authentication, database access, API endpoints, error handling, and tests produces mediocre code at best.
### When to Use It
Decomposition prompting is essential when:
- The task would require **more than 100-200 lines of code** in a single response
- The system has **multiple components** that interact
- Different parts of the system require **different expertise** (frontend vs. backend, algorithms vs. UI)
- You need **high quality** in each component (rather than "good enough" overall)
- The task involves **both design and implementation** decisions
> **Intuition**
>
> Think of decomposition prompting like building a house. You would not hand an architect a single sheet of paper and say "design and build me a house." Instead, you work through phases: foundation plan, framing plan, electrical plan, plumbing plan, finish plan. Each phase gets full attention and can be reviewed independently. Decomposition prompting brings this same discipline to AI code generation.
### The Decomposition Process
**Step 1: Ask the AI to decompose the task.**
I need to build a task management API with the following features: - User registration and authentication - CRUD operations for projects - CRUD operations for tasks within projects - Task assignment to users - Due date tracking with overdue notifications - Activity logging for all changes
Before writing any code, decompose this into independent modules that can be built and tested separately. For each module, list: 1. Its responsibility 2. Its public interface (functions/classes it exposes) 3. Its dependencies on other modules 4. The order in which modules should be built
**Step 2: Build each module with a focused prompt.**
Build Module 1: User Authentication
Requirements: - FastAPI router with /register, /login, /logout, /refresh endpoints - JWT token-based authentication with access and refresh tokens - Password hashing with bcrypt - Pydantic models for all request/response schemas - SQLAlchemy models for User table
Dependencies: None (this is the base module)
Interfaces this must expose: - get_current_user() dependency for protecting other routes - User SQLAlchemy model - UserCreate, UserResponse Pydantic schemas
Quality requirements: - Full type hints - Comprehensive error handling - Docstrings on all public functions - At least 3 test cases per endpoint
**Step 3: Build subsequent modules with awareness of previous ones.**
Build Module 2: Project Management
Here is the User model and authentication dependency from Module 1: [paste relevant interfaces]
Requirements: - CRUD endpoints for projects - Projects belong to a user (creator) - Projects can have multiple members - Only the creator can delete a project - Members can view and edit project details
Build this module to integrate cleanly with the authentication module. Use the get_current_user dependency for route protection.
### Decomposition Strategies
**Top-down decomposition** starts with the high-level architecture and breaks it into progressively smaller pieces:
System → Subsystems → Modules → Classes → Functions
**Bottom-up decomposition** starts with utility functions and builds up:
Helper functions → Core classes → Module integration → System assembly
**Layered decomposition** follows the standard software architecture layers:
Data layer → Business logic layer → API layer → Presentation layer
> **Advanced**
>
> For very large systems, combine decomposition with role-based prompting. Use an "architect" role to create the decomposition plan, a "backend developer" role to implement the data and business layers, a "security engineer" role to review each module, and a "QA engineer" role to generate tests. This multi-role decomposition produces remarkably thorough results.
### When to Decompose and When Not To
**Do decompose** when:
- The task has more than three logical components
- Components have different concerns (data access vs. business logic vs. presentation)
- You need each component to be production-quality
- Different components require different testing strategies
**Do not decompose** when:
- The task is genuinely simple and atomic
- Over-decomposition would create unnecessary complexity
- The components are so tightly coupled that they cannot be meaningfully separated
> **Common Pitfall**
>
> A common mistake is decomposing a task and then losing coherence during integration. Each module works perfectly in isolation but they do not fit together. Prevent this by defining interfaces *before* implementing any module, and including interface contracts in every module's prompt.
---
## 12.6 Constraint Satisfaction Prompting
### What It Is
Constraint satisfaction prompting explicitly lists the requirements and constraints that the generated code must satisfy. Rather than hoping the AI infers your needs from context, you enumerate them as a checklist, and the AI treats each constraint as a hard requirement to meet.
This technique is inspired by constraint satisfaction problems (CSPs) in computer science, where a solution must satisfy every constraint simultaneously. By making constraints explicit, you transform a vague "write good code" request into a concrete, verifiable specification.
### When to Use It
Use constraint satisfaction prompting when:
- You have **specific performance requirements** (response time, memory usage)
- There are **regulatory or compliance requirements** (GDPR, HIPAA, PCI-DSS)
- Your project has **strict coding standards** that must be followed
- You need code that works within **specific technical limitations** (Python version, available libraries)
- The output must meet **multiple simultaneous requirements** that could conflict
### How to Structure Constraint Prompts
The key is to organize constraints into clear categories:
Generate a data processing pipeline for CSV files with the following constraints:
Functional Constraints
- Must handle CSV files up to 10GB in size
- Must support these column types: string, integer, float, datetime, boolean
- Must detect and report malformed rows without crashing
- Must output results in Parquet format
Performance Constraints
- Must process at least 100,000 rows per second on a single core
- Memory usage must not exceed 500MB regardless of file size
- Must support streaming (cannot load entire file into memory)
Compatibility Constraints
- Python 3.10+ only
- Only these external libraries: pandas, pyarrow, click
- Must run on Linux, macOS, and Windows
Code Quality Constraints
- All functions must have type hints
- All public functions must have docstrings (Google style)
- No function longer than 30 lines
- Cyclomatic complexity per function must not exceed 10
- Must include unit tests with at least 80% code coverage target
Error Handling Constraints
- All exceptions must be caught and logged (never silently swallowed)
- Must provide clear error messages with row numbers for data issues
- Must support a --strict mode that fails on first error and a --lenient mode that collects all errors
> **Best Practice**
>
> Number or bullet your constraints. After the AI generates code, you can go through the list and verify each constraint is met. If a constraint was missed, your follow-up prompt is precise: "Constraint 3 under Performance is not met — the code loads the entire file with pd.read_csv(). Please refactor to use chunked reading."
### Hard vs. Soft Constraints
Not all constraints are equally important. Distinguish between hard constraints (must be met) and soft constraints (preferred but negotiable):
Generate a REST API client library with these requirements:
Hard Constraints (must be satisfied)
- Thread-safe: safe to use from multiple threads simultaneously
- Timeout: all requests must have configurable timeouts (default 30s)
- Retry: automatic retry with exponential backoff for 5xx errors
- Type-safe: full type hints, compatible with mypy --strict
Soft Constraints (preferred)
- Connection pooling for performance (ideal but not required)
- Response caching with configurable TTL (nice to have)
- Async support via aiohttp (if it doesn't add excessive complexity)
If any soft constraint would significantly complicate the design or conflict with a hard constraint, explain the trade-off and skip it.
This gives the AI clear prioritization, leading to solutions that always satisfy what matters most while sensibly handling secondary requirements.
### Constraint Verification Pattern
After receiving code, use a follow-up prompt to verify constraints:
Review the code you just generated against these constraints. For each constraint, indicate: - MET: The constraint is fully satisfied (explain how) - PARTIAL: The constraint is partially met (explain what's missing) - NOT MET: The constraint is not satisfied (explain why and suggest a fix)
[paste constraint list]
This creates a natural verification loop that catches missed requirements before they become bugs.
---
## 12.7 Comparative Prompting: "Give Me Two Approaches"
### What It Is
Comparative prompting asks the AI to generate multiple solutions to the same problem and compare their trade-offs. Instead of getting a single answer and hoping it is the best one, you get two or three approaches with explicit analysis of when each is preferable.
This technique is valuable because software engineering rarely has a single "right" answer. The best approach depends on context: team expertise, performance requirements, maintenance burden, scalability needs, and many other factors. Comparative prompting surfaces these trade-offs explicitly.
### When to Use It
Comparative prompting is most valuable when:
- You are making a **design decision** with lasting consequences
- There are **legitimate trade-offs** between approaches (simplicity vs. performance, flexibility vs. type safety)
- You want to **learn** about different ways to solve a problem
- You need to **justify a technical choice** to stakeholders
- You are **unsure** which approach is best for your context
### How to Use It
I need to implement a caching layer for my web application. Give me two approaches:
Approach A: A simple in-memory cache Approach B: A Redis-based distributed cache
For each approach: 1. Provide a complete, working implementation 2. List the pros and cons 3. Describe when this approach is the better choice 4. Estimate the operational complexity (deployment, monitoring, debugging)
Then recommend which approach is better for my context: a web app with 3 server instances serving approximately 10,000 requests per minute, where cache consistency matters moderately (stale data for up to 60 seconds is acceptable).
### The Comparison Matrix Pattern
For more structured comparisons, request a matrix:
I need to choose a state management approach for my Python desktop application (using tkinter). Compare these three approaches:
- Simple global dictionary
- Observer pattern with custom events
- Redux-like store with reducers
For each, provide: - A working code example (a counter with increment/decrement) - Lines of boilerplate code required - Scalability score (1-5) for apps with 50+ state variables - Testability score (1-5) - Learning curve for junior developers (1-5, where 1 is easiest) - A paragraph on when to use it
Present the comparison as a markdown table, then give your recommendation for a medium-complexity desktop application maintained by a team of mixed experience levels.
> **Intuition**
>
> Comparative prompting is like asking a consultant for options rather than a single recommendation. A good consultant does not just tell you "use PostgreSQL" — they say "here are three database options, here are the trade-offs, and given your specific needs, here is what I recommend." Comparative prompting gets you the same depth of analysis from AI.
### When Not to Use Comparative Prompting
Do not use comparative prompting when:
- There is a **clearly superior approach** that everyone agrees on
- You need code **quickly** and the choice does not matter much
- The "comparison" would be between a good approach and an obviously bad one
- You already know which approach you want and just need the implementation
> **Common Pitfall**
>
> Asking for too many approaches dilutes quality. Comparing two approaches produces thorough analysis. Comparing five approaches produces superficial analysis of each. Stick to two or three options at most, and make them genuinely different (not minor variations of the same idea).
---
## 12.8 Socratic Prompting: Learning Through Questions
### What It Is
Socratic prompting inverts the typical vibe coding interaction. Instead of telling the AI what to build, you ask the AI to ask *you* questions that help you clarify your thinking, discover requirements you missed, and understand the problem more deeply before any code is written.
This technique is named after the Socratic method — the philosophical approach of learning through questioning. In the coding context, it turns the AI into a technical consultant who interviews you about your project before recommending solutions.
### When to Use It
Socratic prompting is most valuable when:
- You have a **vague idea** but have not fully thought through the requirements
- You are working in an **unfamiliar domain** and do not know what questions to ask
- You want to ensure you have considered **all edge cases** before building
- You are making a **significant architectural decision** and want to be thorough
- You want to **learn** from the AI's questions (the questions themselves teach you what matters)
### How to Use It
I want to build a rate limiter for my API. Before suggesting any implementation, ask me 10 questions that will help you understand my requirements and constraints well enough to propose the best solution. Ask the questions one topic at a time so I can think through each one carefully.
The AI might respond:
Great questions to think through. Let's start with the basics:
1. Scope and Granularity: What entity should be rate-limited? Options include: - Per user (authenticated requests) - Per IP address (unauthenticated requests) - Per API key - Per endpoint - Some combination
Which applies to your system, and do you need different limits for different entities?
You answer, and it asks the next question:
2. Rate Limit Algorithm: How should the limit window work? - Fixed window (e.g., 100 requests per minute, resets at the minute boundary) - Sliding window (e.g., 100 requests in any 60-second period) - Token bucket (steady refill rate, allows bursts) - Leaky bucket (strict steady rate, no bursts)
Do you have a preference, or would you like me to explain the trade-offs of each?
After ten questions, the AI has a comprehensive understanding of your needs, and you have a clearer picture of your own requirements. The resulting implementation will be far more targeted than what a single "build me a rate limiter" prompt would produce.
> **Real-World Application**
>
> A developer new to building payment systems used Socratic prompting to discover requirements they had not considered: idempotency keys for preventing duplicate charges, webhook signature verification for security, handling partial refunds, dealing with currency precision issues, and compliance logging requirements. The AI's questions surfaced ten critical requirements that the developer would have discovered only after production bugs.
### Combining Socratic with Other Techniques
Socratic prompting works beautifully as a first step before applying other techniques:
1. **Socratic prompting** to discover and clarify requirements
2. **Decomposition prompting** to break the clarified requirements into modules
3. **Constraint satisfaction prompting** to formalize the requirements as constraints
4. **Chain-of-thought prompting** to implement each module with careful reasoning
This four-step pipeline produces remarkably thorough results for complex systems.
### The Guided Socratic Pattern
If you do not want fully open-ended questions, guide the AI's inquiry:
I'm building a notification system. Ask me questions about: 1. Notification channels (what types of notifications?) 2. User preferences (how do users control what they receive?) 3. Delivery guarantees (what happens if delivery fails?) 4. Scale requirements (how many notifications per day?) 5. Content management (who creates notification templates?)
For each area, ask 2-3 specific questions that will help you design the optimal system.
---
## 12.9 Prompt Chaining Across Multiple Interactions
### What It Is
Prompt chaining is the technique of using the output of one prompt as the input to the next, creating a pipeline of prompts that progressively builds toward a complete solution. Each prompt in the chain handles one specific aspect of the task, and the outputs flow from one step to the next.
This differs from simple multi-turn conversation in that prompt chaining is *deliberate and planned*. You design the chain before starting, knowing what each step will produce and how it feeds into the next step.
### When to Use It
Prompt chaining is essential when:
- The task involves **multiple distinct phases** (design, implement, test, document)
- You need to **transform data through several stages**
- The output of one step **fundamentally shapes** the input of the next
- You are building a **pipeline** where each stage needs focused attention
- Total context would **exceed the context window** if attempted in one prompt
### Chain Architecture Patterns
**The Linear Chain:**
Prompt 1 → Output 1 → Prompt 2 → Output 2 → Prompt 3 → Final Output
Example: Design → Implement → Test
**The Fan-Out Chain:**
→ Prompt 2A → Output 2A →
Prompt 1 → Output 1 → Prompt 2B → Output 2B → Prompt 3 (merge) → Prompt 2C → Output 2C →
Example: Design a system, then implement frontend, backend, and database in parallel, then integrate.
**The Iterative Chain:**
Prompt 1 → Output 1 → Evaluate → (if not good enough) → Prompt 2 → Output 2 → Evaluate → ...
Example: Generate code, evaluate quality, refine until quality meets threshold.
### A Complete Chain Example
Here is a five-step prompt chain for building a data validation library:
**Step 1: Design the API**
Design the public API for a Python data validation library. Define: - The main classes and their methods - How users will define validation rules - How validation errors will be reported - How custom validators will be created
Output only the API surface — type-annotated function signatures, class definitions with docstrings, and usage examples. No implementation yet.
**Step 2: Implement core validation engine**
Here is the API design for a validation library: [paste output from Step 1]
Implement the core validation engine: - The Validator base class - The ValidationResult and ValidationError classes - The validate() pipeline that chains multiple validators - Built-in validators: required, type_check, range, pattern
Do not implement custom validator support yet — we will add that in the next step.
**Step 3: Add extensibility**
Here is the core validation engine: [paste output from Step 2]
Now add the extensibility layer: - Custom validator registration via @validator decorator - Composite validators (and_validator, or_validator, not_validator) - Schema-based validation using dictionaries - Nested object validation
Ensure all new code integrates cleanly with the existing engine.
**Step 4: Generate comprehensive tests**
Here is the complete validation library: [paste combined output from Steps 2 and 3]
Generate comprehensive pytest tests covering: - All built-in validators (happy path and edge cases) - Custom validators - Composite validators - Schema validation - Nested object validation - Error message formatting - Edge cases: empty input, None values, wrong types
Organize tests into logical test classes. Aim for >90% coverage.
**Step 5: Generate documentation**
Here is the validation library and its tests: [paste from previous steps]
Generate: 1. A README.md with installation, quick start, and API reference 2. Inline docstrings for any functions that are missing them 3. Three usage examples of increasing complexity 4. A migration guide for users coming from cerberus or marshmallow
> **Best Practice**
>
> When chaining prompts, always paste the relevant output from the previous step — not all of it, but the parts that the next step needs. This keeps each prompt focused and avoids context window bloat. For large outputs, paste only the interfaces (function signatures, class definitions) rather than full implementations when the next step does not need implementation details.
### Managing Chain State
For long chains, maintain a "state document" that summarizes decisions made so far:
Chain State (update after each step)
Architecture Decisions
- Using FastAPI for REST API
- PostgreSQL with SQLAlchemy 2.0 async
- JWT authentication with refresh tokens
Completed Modules
- User model and auth routes (Step 2)
- Project CRUD (Step 3)
Interfaces Defined
- get_current_user() -> User dependency
- ProjectRepository.create(), .get(), .update(), .delete()
Next Step
- Task CRUD within projects (Step 4)
- Needs: Project model, auth dependency
Include this state document at the beginning of each prompt in the chain. It gives the AI context without requiring you to paste all previous code.
> **Common Pitfall**
>
> The biggest risk in prompt chaining is *context drift* — where later prompts subtly contradict decisions made in earlier prompts. For example, Step 1 might define a method signature as `validate(data: dict) -> bool`, but Step 3 quietly changes it to `validate(data: dict) -> ValidationResult`. Prevent this by including interface contracts in every step and explicitly noting "do not change any previously defined interfaces."
---
## 12.10 Building Your Personal Prompt Library
### What It Is
A personal prompt library is a curated, organized collection of prompts that have proven effective for tasks you do regularly. Instead of crafting prompts from scratch every time, you maintain a library of templates that you can quickly customize and reuse.
Think of it as the prompting equivalent of a code snippet library — but more powerful, because prompts encode not just code patterns but *communication strategies* that consistently produce high-quality AI output.
### Why Build a Prompt Library
**Consistency.** When multiple team members use the same prompts, the generated code follows the same patterns and conventions.
**Efficiency.** A well-crafted prompt might take 20 minutes to develop through iteration. Reusing it saves that time on every subsequent use.
**Institutional knowledge.** Your prompt library captures what your team has learned about effective AI communication. It survives personnel changes.
**Improvement over time.** Each time you use a prompt, you can refine it. Version by version, your prompts get better.
### Library Organization
Organize your prompt library along multiple dimensions:
**By technique:**
- Chain-of-thought prompts
- Few-shot prompts
- Role-based prompts
- Decomposition templates
- Constraint satisfaction templates
**By task type:**
- Code generation (new features)
- Code review
- Bug fixing
- Testing
- Documentation
- Refactoring
- Architecture design
**By domain:**
- Web development (frontend, backend, full-stack)
- Data engineering
- Machine learning
- DevOps and infrastructure
- Mobile development
### A Practical Prompt Library System
Here is a lightweight system using YAML files:
```yaml
# prompts/code-generation/rest-endpoint.yaml
name: "REST Endpoint Generator"
version: "2.3"
technique: "constraint-satisfaction"
tags: ["backend", "api", "fastapi"]
author: "team"
last_updated: "2025-11-15"
effectiveness_rating: 4.5 # out of 5, based on team feedback
template: |
Generate a {method} endpoint for {resource} using FastAPI.
## Endpoint Specification
- Route: {route}
- Method: {method}
- Authentication: {auth_required}
## Request Schema
{request_schema}
## Response Schema
{response_schema}
## Quality Requirements
- Full type hints (compatible with mypy --strict)
- Google-style docstrings
- Pydantic v2 models for all schemas
- Proper HTTP status codes
- Error handling with custom exception classes
- At least 3 unit tests
## Our Conventions
- All routes prefixed with /api/v1/
- Use dependency injection for database sessions
- Return envelope format: {"data": ..., "meta": {...}}
- Log all requests with correlation IDs
variables:
method:
description: "HTTP method"
options: ["GET", "POST", "PUT", "PATCH", "DELETE"]
resource:
description: "The resource name (e.g., 'user', 'project')"
route:
description: "The full route path"
auth_required:
description: "Whether authentication is required"
default: "Yes, JWT bearer token via get_current_user dependency"
request_schema:
description: "Description or example of expected request body"
response_schema:
description: "Description or example of expected response"
usage_notes: |
Works best when you provide concrete field names in the schemas
rather than just descriptions. Include field types and constraints
for best results.
example_usage: |
Filled template for creating a project endpoint:
- method: POST
- resource: project
- route: /api/v1/projects
- request_schema: {"name": "string (1-100 chars)", ...}
Versioning and Improvement
Treat prompts like code — version them and improve them over time:
changelog:
- version: "2.3"
date: "2025-11-15"
changes: "Added correlation ID logging requirement"
- version: "2.2"
date: "2025-10-28"
changes: "Updated to Pydantic v2 syntax"
- version: "2.1"
date: "2025-10-01"
changes: "Added envelope response format requirement"
- version: "2.0"
date: "2025-09-15"
changes: "Major rewrite based on 3 months of team usage data"
- version: "1.0"
date: "2025-06-20"
changes: "Initial version"
Best Practice
Review your prompt library monthly. Archive prompts that are no longer relevant. Update prompts when your team's conventions change. Track which prompts get the best results and study what makes them effective — then apply those lessons to your weaker prompts.
Sharing Prompt Libraries
For teams, store the prompt library in your code repository:
project/
.prompts/
README.md
code-generation/
rest-endpoint.yaml
database-model.yaml
test-suite.yaml
code-review/
security-review.yaml
performance-review.yaml
architecture/
system-design.yaml
decomposition.yaml
meta/
prompt-improver.yaml
prompt-evaluator.yaml
This makes prompts subject to the same review process as code. Team members can submit new prompts via pull requests, and the team can collectively maintain quality.
Advanced
Build tooling around your prompt library. A simple Python script can load a YAML template, prompt you for variables, fill them in, and copy the resulting prompt to your clipboard. Some teams build CLI tools that integrate their prompt library directly with their AI coding assistant, automating the template-filling process entirely. See the code examples in this chapter's
code/directory for a working implementation.
Getting Started
You do not need to build a comprehensive library on day one. Start with three prompts:
- Your most common coding task. Whatever you ask AI to do most often, write a really good prompt for it and save it.
- A code review prompt. Use role-based prompting (Section 12.3) to create a thorough review prompt for your project's language and framework.
- A meta-prompt. Save the meta-prompt template from Section 12.4 so you can use it to generate new prompts as needed.
Build from there. Every time you craft a prompt that works particularly well, add it to your library. Within a month, you will have a collection that meaningfully accelerates your daily work.
Combining Techniques: A Decision Framework
With ten techniques in your toolkit, the natural question is: which one should I use? Here is a decision framework based on the nature of your task:
| Task Characteristic | Recommended Technique(s) |
|---|---|
| Complex algorithm or logic | Chain-of-thought (12.1) |
| Must match existing code patterns | Few-shot (12.2) |
| Needs specialized expertise | Role-based (12.3) |
| Need help writing better prompts | Meta-prompting (12.4) |
| Large system with many parts | Decomposition (12.5) |
| Strict requirements and standards | Constraint satisfaction (12.6) |
| Multiple valid approaches | Comparative (12.7) |
| Unclear requirements | Socratic (12.8) |
| Multi-phase project | Prompt chaining (12.9) |
| Repeated task types | Prompt library (12.10) |
Most real-world tasks benefit from combining two or three techniques. Common powerful combinations include:
- Socratic + Decomposition + Chain-of-Thought: Clarify requirements, break into modules, reason carefully through each module.
- Role-Based + Constraint Satisfaction: Activate specific expertise and enforce specific requirements.
- Few-Shot + Constraint Satisfaction: Show the pattern you want and specify the requirements it must meet.
- Meta-Prompting + Prompt Library: Use AI to optimize prompts, then save the best ones for reuse.
Intuition
Do not try to use every technique in every prompt. That would be like using every cooking technique on every dish — the result would be a confusing mess, not a masterpiece. Start with one or two techniques that match your task. Add more only if the result needs improvement. The goal is the best code with the least prompting complexity.
Putting It All Together: A Real-World Workflow
Let us walk through how an experienced vibe coder might use these techniques together on a realistic project — building a webhook delivery system.
Phase 1: Requirements Discovery (Socratic Prompting)
I need to build a webhook delivery system. Before suggesting any
implementation, ask me 8-10 questions about my requirements,
delivery guarantees, and constraints.
After answering the AI's questions, you have clarified: you need at-least-once delivery, exponential backoff for retries, HMAC signature verification, delivery logging, and a dead letter queue.
Phase 2: Architecture Design (Role-Based + Decomposition)
Act as a senior distributed systems architect. Given these
requirements for a webhook delivery system:
[paste requirements from Phase 1]
Decompose this into independent modules with clear interfaces.
For each module, define its responsibility, public API, and
dependencies.
Phase 3: Implementation (Chain-of-Thought + Constraint Satisfaction)
For each module identified in Phase 2:
Implement the WebhookDispatcher module. Before coding, reason
through the retry logic step by step, including how exponential
backoff interacts with the dead letter queue.
Constraints:
- Max 5 retry attempts
- Backoff: 1s, 5s, 25s, 125s, 625s
- Move to dead letter queue after all retries exhausted
- Log every attempt with timestamp, status code, and duration
- Thread-safe: multiple webhooks can be dispatched concurrently
- Type hints and docstrings on all public methods
Phase 4: Review (Role-Based + Comparative)
Review this webhook dispatcher from two perspectives:
1. As a reliability engineer: Will this actually achieve
at-least-once delivery? What failure modes are unhandled?
2. As a security engineer: Are there injection risks in
URL handling? Is the HMAC implementation correct?
This four-phase workflow uses five techniques and produces a thoroughly designed, carefully implemented, and critically reviewed system. Each technique contributes something different: Socratic prompting ensures completeness, role-based prompting activates expertise, decomposition manages complexity, chain-of-thought ensures algorithmic correctness, and comparative role-based review catches issues.
Summary
This chapter introduced ten advanced prompting techniques that go beyond the fundamentals covered in Chapter 8. Each technique is a distinct approach to AI communication, suited to different types of tasks:
- Chain-of-thought prompting makes AI reason explicitly before coding, improving algorithmic correctness.
- Few-shot prompting teaches AI your patterns through examples, ensuring stylistic consistency.
- Role-based prompting activates domain-specific expertise and evaluation criteria.
- Meta-prompting uses AI to improve your prompts, creating a virtuous improvement cycle.
- Decomposition prompting breaks complex systems into manageable, high-quality pieces.
- Constraint satisfaction prompting makes requirements explicit and verifiable.
- Comparative prompting generates multiple approaches with explicit trade-off analysis.
- Socratic prompting discovers hidden requirements through AI-guided questioning.
- Prompt chaining connects multiple prompts in planned pipelines for multi-phase tasks.
- Prompt libraries capture and organize your most effective prompts for reuse.
The most effective vibe coders do not use these techniques in isolation — they combine them strategically based on the task at hand. Start by mastering one or two techniques for your most common tasks, then gradually expand your repertoire as you encounter new challenges.
In Chapter 13, we will apply these techniques at scale, tackling the unique challenges of working with multiple files and large codebases where context management becomes critical.
Next: Chapter 13: Working with Multiple Files and Large Codebases
Previous: Chapter 11: Iterative Refinement and Conversation Patterns
Related Reading
Explore this topic in other books
Vibe Coding Prompt Engineering Fundamentals Vibe Coding Specification-Driven Prompting AI Engineering Prompt Engineering