# Examples and Few-Shot Patterns: Teaching AI to Code Your Way
When you ask someone to write code for you, showing them examples of what you want is often more effective than lengthy explanations. The same principle applies to AI-assisted coding. Few-shot prompting—providing examples to guide the AI's output—is one of the most powerful techniques in your vibe coding toolkit.
In this lesson, you'll learn how to use examples strategically to get AI assistants to generate code that matches your style, follows your patterns, and integrates seamlessly with your codebase.
## What Is Few-Shot Prompting?
Few-shot prompting means including one or more examples in your prompt to demonstrate the pattern or style you want the AI to follow. Instead of just describing what you want, you show it.
**Zero-shot** (no examples):
```
Create a user validation function
```
**One-shot** (one example):
```
Create a user validation function following this pattern:
function validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
Now create validateUsername with similar structure.
```
**Few-shot** (multiple examples):
```
Create validation functions following these patterns:
function validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
function validatePassword(password) {
const minLength = 8;
const hasNumber = /\d/.test(password);
return password.length >= minLength && hasNumber;
}
Now create validateUsername (3-20 chars, alphanumeric only).
```
The more examples you provide, the better the AI understands your intent, style, and conventions.
## Why Examples Matter in Code Generation
### 1. Style Consistency
Examples teach AI your coding style without writing a style guide:
```javascript
// Show your preferred error handling pattern
const fetchUser = async (id) => {
try {
const response = await api.get(`/users/${id}`);
return { data: response.data, error: null };
} catch (error) {
return { data: null, error: error.message };
}
};
// AI will now match this pattern for other fetch functions
```
### 2. Project-Specific Patterns
Every codebase has its conventions. Examples ensure AI follows yours:
```python
# Show your data access pattern
class UserRepository:
def __init__(self, db):
self.db = db
def find_by_id(self, user_id):
return self.db.query(User).filter(User.id == user_id).first()
# Now ask for ProductRepository following the same pattern
```
### 3. Complex Transformations
Some operations are easier to show than tell:
```typescript
// Input transformation example
const apiResponse = {
user_name: "john_doe",
user_email: "john@example.com",
account_created: "2024-01-15"
};
// Desired output
const frontendModel = {
userName: "john_doe",
userEmail: "john@example.com",
accountCreated: new Date("2024-01-15")
};
// Now transform the product API response similarly
```
## Crafting Effective Few-Shot Prompts
### Start with Clear Context
Always frame your examples with context:
```
I'm building a REST API with Express. Here's how I structure route handlers:
[examples]
Create a similar handler for updating user profiles.
```
### Use Progressive Examples
Show examples that increase in complexity:
```javascript
// Example 1: Simple case
function formatDate(date) {
return date.toISOString().split('T')[0];
}
// Example 2: With options
function formatCurrency(amount, currency = 'USD') {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency
}).format(amount);
}
// Example 3: With validation
function formatPhoneNumber(phone) {
if (!/^\d{10}$/.test(phone)) {
throw new Error('Invalid phone number');
}
return `(${phone.slice(0,3)}) ${phone.slice(3,6)}-${phone.slice(6)}`;
}
// Now create formatAddress(address) with validation
```
### Highlight Edge Cases
Include examples showing how to handle special situations:
```python
# Example: Handling None values
def get_user_display_name(user):
if user is None:
return "Guest"
return user.full_name or user.email or "Unknown User"
# Example: Handling empty lists
def get_latest_order(orders):
if not orders:
return None
return max(orders, key=lambda o: o.created_at)
# Create get_primary_address(addresses) with similar safety checks
```
## Pattern Recognition Techniques
### Show Input-Output Pairs
For data transformation tasks, provide clear input-output examples:
```
Transform database records to API responses:
Input:
{
"id": 1,
"created_at": "2024-01-15T10:30:00Z",
"is_active": true,
"user_type": "premium"
}
Output:
{
"id": 1,
"createdAt": "2024-01-15T10:30:00Z",
"active": true,
"type": "premium"
}
Now transform these product records [provide records].
```
### Demonstrate Test Patterns
Show how you write tests to get consistent test generation:
```javascript
describe('validateEmail', () => {
it('accepts valid emails', () => {
expect(validateEmail('user@example.com')).toBe(true);
expect(validateEmail('name.surname@domain.co.uk')).toBe(true);
});
it('rejects invalid emails', () => {
expect(validateEmail('invalid')).toBe(false);
expect(validateEmail('@example.com')).toBe(false);
expect(validateEmail('user@')).toBe(false);
});
it('handles edge cases', () => {
expect(validateEmail('')).toBe(false);
expect(validateEmail(null)).toBe(false);
});
});
// Create similar tests for validateUsername
```
This connects directly to [testing-strategies](testing-strategies) where you'll learn comprehensive testing approaches.
### Template Completion
Provide a template with gaps for AI to fill:
```typescript
interface User {
id: string;
email: string;
name: string;
}
interface Product {
id: string;
title: string;
price: number;
}
// Create similar interface for Order with:
// - id (string)
// - userId (string)
// - items (array of {productId: string, quantity: number})
// - total (number)
// - status (enum: pending, completed, cancelled)
```
## Practical Few-Shot Patterns
### Pattern 1: API Client Methods
When building API integrations, show the pattern once:
```javascript
class GitHubClient {
constructor(token) {
this.token = token;
this.baseURL = 'https://api.github.com';
}
async getUser(username) {
const response = await fetch(`${this.baseURL}/users/${username}`, {
headers: { 'Authorization': `token ${this.token}` }
});
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.statusText}`);
}
return response.json();
}
async getRepo(owner, repo) {
const response = await fetch(`${this.baseURL}/repos/${owner}/${repo}`, {
headers: { 'Authorization': `token ${this.token}` }
});
if (!response.ok) {
throw new Error(`Failed to fetch repo: ${response.statusText}`);
}
return response.json();
}
}
// Add methods for:
// - getIssues(owner, repo)
// - createIssue(owner, repo, title, body)
```
The AI will maintain consistent error handling, headers, and structure. For larger codebases, check out [codebase-aware-prompting](codebase-aware-prompting).
### Pattern 2: Database Queries
Show your ORM/query patterns:
```python
# Example queries in our style
class UserQueries:
@staticmethod
def find_active_users():
return db.session.query(User).filter(
User.is_active == True
).order_by(User.created_at.desc()).all()
@staticmethod
def find_by_email(email):
return db.session.query(User).filter(
User.email == email
).first()
# Create similar queries for Product model:
# - find_in_stock()
# - find_by_category(category)
```
### Pattern 3: Component Structure
For frontend work, show your component architecture:
```jsx
// Example components
const Button = ({ onClick, children, variant = 'primary', disabled = false }) => {
const className = `btn btn-${variant} ${disabled ? 'btn-disabled' : ''}`;
return (
);
};
const Input = ({ value, onChange, placeholder, type = 'text', error }) => {
return (
{error && {error}}
);
};
// Create a Select component following the same patterns
```
For more on generating UI components, see [component-generation](component-generation).
### Pattern 4: Error Handling
Demonstrate your error handling philosophy:
```typescript
// Example 1: Service layer
async function createUser(userData: UserInput): Promise> {
try {
const validated = validateUserInput(userData);
const user = await db.users.create(validated);
await sendWelcomeEmail(user.email);
return { success: true, data: user };
} catch (error) {
logger.error('User creation failed', { error, userData });
return { success: false, error: error.message };
}
}
// Example 2: Controller layer
async function handleCreateUser(req: Request, res: Response) {
const result = await createUser(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error });
}
return res.status(201).json(result.data);
}
// Create similar functions for updateUser and deleteUser
```
This ties into [error-resolution](error-resolution) and [debugging-workflows](debugging-workflows) for handling issues that arise.
## Advanced Few-Shot Techniques
### Chaining Examples with Context
Combine few-shot with contextual information:
```
In our e-commerce system, we use a repository pattern with these conventions:
1. All repos extend BaseRepository
2. Methods return Either
3. We use TypeScript strict mode
Example:
class OrderRepository extends BaseRepository {
async findByUser(userId: string): Promise> {
try {
const orders = await this.model.find({ userId }).exec();
return right(orders);
} catch (error) {
return left(new DatabaseError(error.message));
}
}
}
Create PaymentRepository with methods:
- findByOrder(orderId)
- findSuccessful()
```
For handling multi-step reasoning, explore [chain-of-thought](chain-of-thought) prompting.
### Negative Examples
Sometimes showing what NOT to do is powerful:
```javascript
// ❌ DON'T: Nested callbacks
getUser(id, (user) => {
getOrders(user.id, (orders) => {
getProducts(orders[0].id, (products) => {
// callback hell
});
});
});
// ✅ DO: Async/await
async function getUserWithOrders(id) {
const user = await getUser(id);
const orders = await getOrders(user.id);
const products = await getProducts(orders[0].id);
return { user, orders, products };
}
// Convert this callback-based code to async/await: [provide code]
```
### Combining with Comments
Annotate examples to explain the reasoning:
```python
def process_payment(amount, currency):
# Convert to cents to avoid floating point issues
amount_cents = int(amount * 100)
# Validate before processing
if amount_cents < 50: # Minimum $0.50
raise ValueError("Amount too small")
# Use idempotency key to prevent duplicate charges
idempotency_key = generate_idempotency_key()
return payment_gateway.charge(
amount=amount_cents,
currency=currency,
idempotency_key=idempotency_key
)
# Create process_refund following similar patterns and safety checks
```
## Optimizing for Token Efficiency
Examples consume tokens from your context window. Be strategic:
### Use Minimal Complete Examples
Include just enough to demonstrate the pattern:
```typescript
// Instead of full implementation
class UserService {
constructor(private repo: UserRepository) {}
async create(data: UserDTO): Promise {
const validated = this.validate(data);
return this.repo.save(validated);
}
// ... 20 more methods
}
// Show concise examples
class UserService {
async create(data: UserDTO): Promise {
const validated = this.validate(data);
return this.repo.save(validated);
}
}
// Create ProductService with similar structure
```
Learn more about managing your AI's memory in [context-window-management](context-window-management).
### Reference Instead of Repeating
For consistent patterns across your codebase:
```
Create a new API endpoint for /products following the same pattern as:
- /users (see routes/users.js)
- /orders (see routes/orders.js)
Include: GET, POST, PUT, DELETE with authentication middleware.
```
## Common Pitfalls and Solutions
### Pitfall 1: Too Many Examples
**Problem**: Including 10 examples when 2 would suffice wastes tokens.
**Solution**: Start with 1-2 examples. Add more only if output quality suffers.
### Pitfall 2: Inconsistent Examples
**Problem**: Examples contradict each other.
```javascript
// Example 1 uses async/await
async function getData() { ... }
// Example 2 uses promises
function getData() { return fetch().then() }
```
**Solution**: Ensure all examples follow the same conventions.
### Pitfall 3: Overly Complex Examples
**Problem**: Examples too complex to understand the core pattern.
**Solution**: Simplify to essential elements:
```javascript
// Too complex
function complexUserValidation(user, options, context, callbacks) {
// 50 lines of validation logic
}
// Better
function validateUser(user) {
if (!user.email) throw new Error('Email required');
if (!user.name) throw new Error('Name required');
return true;
}
```
This connects to [code-gen-best-practices](code-gen-best-practices) for writing cleaner prompts.
### Pitfall 4: Outdated Examples
**Problem**: Examples use deprecated patterns or old syntax.
**Solution**: Regularly update your example library. Keep a prompt file with current best practices.
## Building Your Example Library
Create a personal collection of proven examples:
### 1. Organize by Category
```
my-prompts/
├── api-patterns/
│ ├── rest-endpoints.md
│ ├── error-handling.md
│ └── authentication.md
├── database/
│ ├── queries.md
│ └── migrations.md
└── frontend/
├── components.md
└── hooks.md
```
### 2. Document Context
For each example, note:
- When to use it
- What pattern it demonstrates
- Any gotchas or edge cases
### 3. Version Your Examples
As your codebase evolves, update examples to match current standards. This prevents the AI from generating outdated code.
## Practical Exercise
Try this progressive exercise:
**Step 1**: Get AI to generate a simple function without examples:
```
Create a function to validate email addresses
```
**Step 2**: Now provide one example:
```
Create email validation following this pattern:
function validateUsername(username) {
const regex = /^[a-zA-Z0-9_]{3,20}$/;
if (!regex.test(username)) {
return { valid: false, error: 'Invalid username format' };
}
return { valid: true };
}
```
**Step 3**: Compare the outputs. The second should match your error handling style and return format.
For refining AI output, see [iterating-output](iterating-output).
## Integration with Your Workflow
Few-shot prompting works best when:
1. **Starting new features**: Show existing similar features
2. **Maintaining consistency**: Reference established patterns
3. **Onboarding to codebases**: Examples teach AI your conventions quickly
4. **Generating tests**: Show one test suite, get matching test suites
Combine few-shot patterns with other techniques like [review-refactor](review-refactor) to ensure quality output and [quality-control](quality-control) to maintain standards.
## When to Use Few-Shot vs. Other Approaches
**Use few-shot when**:
- You have clear existing patterns to follow
- Consistency across code is critical
- Explaining in words is harder than showing
**Consider alternatives when**:
- You're exploring new patterns (see [ai-architecture](ai-architecture))
- Examples would be too lengthy (see [codebase-aware-prompting](codebase-aware-prompting))
- You need step-by-step reasoning (see [chain-of-thought](chain-of-thought))
## Key Takeaways
1. **Show, don't just tell**: Examples are more effective than descriptions for teaching AI your coding style
2. **Start minimal**: Use 1-2 examples, add more only if needed
3. **Be consistent**: All examples should follow the same conventions
4. **Annotate when helpful**: Comments explain the "why" behind patterns
5. **Build a library**: Save proven examples for reuse
6. **Combine techniques**: Few-shot works great with other prompting strategies
Mastering few-shot prompting transforms AI from a generic code generator into a tool that writes code that looks like YOU wrote it. Practice identifying patterns in your codebase, and you'll quickly build an intuition for when and how to use examples effectively.
Next, dive deeper into [chain-of-thought](chain-of-thought) prompting to handle complex reasoning tasks, or explore [working-with-generated](working-with-generated) code to refine AI output further.