Examples and Few-Shot Patterns

15 minpublished

Use example-based prompting to teach AI assistants your preferred coding patterns and conventions.

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:

// 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:

# 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:

// 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:

// 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:

# 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:

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 where you'll learn comprehensive testing approaches.

Template Completion

Provide a template with gaps for AI to fill:

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:

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.

Pattern 2: Database Queries

Show your ORM/query patterns:

# 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:

// Example components
const Button = ({ onClick, children, variant = 'primary', disabled = false }) => {
  const className = `btn btn-${variant} ${disabled ? 'btn-disabled' : ''}`;
  
  return (
    <button 
      onClick={onClick} 
      disabled={disabled}
      className={className}
    >
      {children}
    </button>
  );
};

const Input = ({ value, onChange, placeholder, type = 'text', error }) => {
  return (
    <div className="input-wrapper">
      <input
        type={type}
        value={value}
        onChange={onChange}
        placeholder={placeholder}
        className={error ? 'input-error' : 'input'}
      />
      {error && <span className="error-message">{error}</span>}
    </div>
  );
};

// Create a Select component following the same patterns

For more on generating UI components, see component-generation.

Pattern 4: Error Handling

Demonstrate your error handling philosophy:

// Example 1: Service layer
async function createUser(userData: UserInput): Promise<Result<User>> {
  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 and 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<Error, Data>
3. We use TypeScript strict mode

Example:

class OrderRepository extends BaseRepository<Order> {
  async findByUser(userId: string): Promise<Either<Error, Order[]>> {
    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 prompting.

Negative Examples

Sometimes showing what NOT to do is powerful:

// ❌ 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:

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:

// Instead of full implementation
class UserService {
  constructor(private repo: UserRepository) {}
  
  async create(data: UserDTO): Promise<User> {
    const validated = this.validate(data);
    return this.repo.save(validated);
  }
  
  // ... 20 more methods
}

// Show concise examples
class UserService {
  async create(data: UserDTO): Promise<User> {
    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.

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.

// 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:

// 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 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.

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 to ensure quality output and 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:

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 prompting to handle complex reasoning tasks, or explore working-with-generated code to refine AI output further.