After 6 months with GitHub Copilot, I’ve discovered that the quality of suggestions depends heavily on how you “prompt” it. Here are the techniques that dramatically improved my results.

Table of contents

The Copilot Prompt Model

GitHub Copilot uses your code context as a prompt:

  • Current file content
  • File name and extension
  • Comments and docstrings
  • Variable and function names
  • Imported libraries

Understanding this helps you write better “prompts.”

Technique 1: Descriptive Comments

Bad Example

# Get user
def get_user(id):

Copilot suggestion:

def get_user(id):
    return User.query.get(id)

Generic, minimal error handling.

Good Example

# Fetch user by ID from database with error handling
# Returns None if user not found
# Raises DatabaseError if connection fails
def get_user(user_id: int) -> Optional[User]:

Copilot suggestion:

def get_user(user_id: int) -> Optional[User]:
    try:
        user = User.query.get(user_id)
        if not user:
            logger.warning(f"User {user_id} not found")
            return None
        return user
    except SQLAlchemyError as e:
        logger.error(f"Database error fetching user {user_id}: {e}")
        raise DatabaseError(f"Failed to fetch user: {e}")

Much better! Includes logging, error handling, and type hints.

Technique 2: Function Naming

Bad Example

function process(data) {

Copilot suggestion:

function process(data) {
  return data;
}

Useless.

Good Example

function validateAndSanitizeUserInput(rawInput) {

Copilot suggestion:

function validateAndSanitizeUserInput(rawInput) {
  // Remove HTML tags
  const sanitized = rawInput.replace(/<[^>]*>/g, '');
  
  // Trim whitespace
  const trimmed = sanitized.trim();
  
  // Validate length
  if (trimmed.length === 0 || trimmed.length > 1000) {
    throw new Error('Input must be between 1 and 1000 characters');
  }
  
  // Escape special characters
  return trimmed
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
}

Excellent! Descriptive name led to comprehensive implementation.

Technique 3: Type Hints and Interfaces

TypeScript Example

interface UserProfile {
  id: number;
  email: string;
  firstName: string;
  lastName: string;
  createdAt: Date;
  isActive: boolean;
}

// Transform API response to UserProfile
function transformApiResponseToUserProfile(apiResponse: any): UserProfile {

Copilot suggestion:

function transformApiResponseToUserProfile(apiResponse: any): UserProfile {
  return {
    id: apiResponse.user_id,
    email: apiResponse.email_address,
    firstName: apiResponse.first_name,
    lastName: apiResponse.last_name,
    createdAt: new Date(apiResponse.created_at),
    isActive: apiResponse.status === 'active'
  };
}

Perfect transformation based on interface definition.

Technique 4: Example-Driven Development

Pattern: Show Examples

# Convert snake_case to camelCase
# Examples:
# user_name -> userName
# first_name -> firstName
# api_key_value -> apiKeyValue
def snake_to_camel(snake_str: str) -> str:

Copilot suggestion:

def snake_to_camel(snake_str: str) -> str:
    components = snake_str.split('_')
    return components[0] + ''.join(x.title() for x in components[1:])

Examples helped Copilot understand the exact transformation needed.

Technique 5: Context Through Imports

Before

def hash_password(password):

Copilot suggestion:

def hash_password(password):
    return hashlib.sha256(password.encode()).hexdigest()

Insecure! SHA256 is not suitable for passwords.

After

from werkzeug.security import generate_password_hash, check_password_hash

def hash_password(password: str) -> str:

Copilot suggestion:

def hash_password(password: str) -> str:
    return generate_password_hash(password, method='pbkdf2:sha256', salt_length=16)

Much better! Import context guided Copilot to use proper library.

Technique 6: Structured Comments

Template Pattern

"""
Function: Calculate compound interest
Input: principal (float), rate (float), time (int), frequency (int)
Output: float
Formula: A = P(1 + r/n)^(nt)
Edge cases: Handle negative values, zero principal
"""
def calculate_compound_interest(
    principal: float,
    rate: float,
    time: int,
    frequency: int = 12
) -> float:

Copilot suggestion:

def calculate_compound_interest(
    principal: float,
    rate: float,
    time: int,
    frequency: int = 12
) -> float:
    # Validate inputs
    if principal < 0:
        raise ValueError("Principal cannot be negative")
    if rate < 0:
        raise ValueError("Rate cannot be negative")
    if time < 0:
        raise ValueError("Time cannot be negative")
    if frequency <= 0:
        raise ValueError("Frequency must be positive")
    
    # Handle zero principal
    if principal == 0:
        return 0.0
    
    # Calculate compound interest: A = P(1 + r/n)^(nt)
    amount = principal * (1 + rate / frequency) ** (frequency * time)
    
    return round(amount, 2)

Comprehensive implementation with all requirements met.

Technique 7: Test-Driven Prompting

Write Tests First

def test_parse_date():
    assert parse_date("2022-01-15") == datetime(2022, 1, 15)
    assert parse_date("15/01/2022") == datetime(2022, 1, 15)
    assert parse_date("Jan 15, 2022") == datetime(2022, 1, 15)
    assert parse_date("invalid") is None

# Parse various date formats into datetime object
def parse_date(date_str: str) -> Optional[datetime]:

Copilot suggestion:

def parse_date(date_str: str) -> Optional[datetime]:
    formats = [
        "%Y-%m-%d",
        "%d/%m/%Y",
        "%b %d, %Y"
    ]
    
    for fmt in formats:
        try:
            return datetime.strptime(date_str, fmt)
        except ValueError:
            continue
    
    return None

Tests provided clear specification for Copilot.

Technique 8: File-Level Context

Good File Structure

# user_service.py
"""
User service module for handling user-related operations.
Uses PostgreSQL database with SQLAlchemy ORM.
Implements caching with Redis.
All functions include error handling and logging.
"""

from sqlalchemy.orm import Session
from redis import Redis
import logging

logger = logging.getLogger(__name__)
redis_client = Redis(host='localhost', port=6379)

# Now Copilot knows the context for all functions in this file

Technique 9: Incremental Refinement

Step 1: Basic Function

# Fetch user from database
def get_user(user_id: int):

Copilot gives basic implementation.

Step 2: Add Caching

# Fetch user from database with Redis caching
def get_user(user_id: int):

Copilot adds caching logic.

Step 3: Add Error Handling

# Fetch user from database with Redis caching and error handling
def get_user(user_id: int) -> Optional[User]:

Copilot adds try-catch blocks.

Technique 10: Language-Specific Patterns

Python: Docstrings

def calculate_discount(price: float, discount_percent: float) -> float:
    """
    Calculate discounted price.
    
    Args:
        price: Original price in dollars
        discount_percent: Discount percentage (0-100)
        
    Returns:
        Discounted price rounded to 2 decimal places
        
    Raises:
        ValueError: If price is negative or discount is not in range 0-100
        
    Examples:
        >>> calculate_discount(100.0, 20.0)
        80.0
        >>> calculate_discount(50.0, 10.0)
        45.0
    """

Copilot generates complete implementation matching docstring.

JavaScript: JSDoc

/**
 * Debounce function to limit execution rate
 * @param {Function} func - Function to debounce
 * @param {number} wait - Wait time in milliseconds
 * @returns {Function} Debounced function
 * @example
 * const debouncedSearch = debounce(searchAPI, 300);
 */
function debounce(func, wait) {

Copilot provides proper debounce implementation.

Real-World Examples

Example 1: API Client

// RESTful API client for user management
// Base URL: https://api.example.com/v1
// Includes authentication, error handling, and retry logic
class UserAPIClient {
  private baseURL: string;
  private apiKey: string;
  private maxRetries: number = 3;
  
  constructor(apiKey: string) {
    this.baseURL = 'https://api.example.com/v1';
    this.apiKey = apiKey;
  }
  
  // Fetch user by ID with automatic retry on failure
  async getUser(userId: number): Promise<User> {

Copilot generates complete method with fetch, headers, retry logic, and error handling.

Example 2: Data Validation

from pydantic import BaseModel, validator, EmailStr
from typing import Optional
from datetime import datetime

class UserRegistration(BaseModel):
    """User registration data model with validation"""
    email: EmailStr
    password: str
    first_name: str
    last_name: str
    age: Optional[int] = None
    
    @validator('password')
    def validate_password(cls, v):
        """
        Password must be:
        - At least 8 characters
        - Contain uppercase and lowercase
        - Contain at least one digit
        - Contain at least one special character
        """

Copilot generates comprehensive password validation.

Measuring Improvement

I tracked suggestion quality over 2 months:

Before Prompt Engineering

  • Acceptance rate: 35%
  • Modifications needed: 80%
  • Time saved: 15%

After Prompt Engineering

  • Acceptance rate: 65%
  • Modifications needed: 40%
  • Time saved: 40%

Result: 86% improvement in acceptance rate.

Common Mistakes

Mistake 1: Vague Comments

# Do something with data
def process(data):

Be specific about what “something” is.

Mistake 2: Inconsistent Naming

def getUserData(id):  # camelCase
def fetch_user_profile(user_id):  # snake_case

Inconsistency confuses Copilot. Stick to one convention.

Mistake 3: Missing Context

# In a file with no imports or context
def encrypt(data):

Copilot doesn’t know what encryption method to use.

Mistake 4: Overly Generic Names

def handler(event, context):

What kind of handler? What events?

Best Practices Summary

  1. Write detailed comments before functions
  2. Use descriptive names for functions and variables
  3. Include type hints (Python) or types (TypeScript)
  4. Show examples in comments
  5. Import relevant libraries first
  6. Write tests before implementation
  7. Structure files with clear context
  8. Be consistent with naming conventions
  9. Iterate incrementally for complex functions
  10. Review and refine Copilot suggestions

Conclusion

GitHub Copilot is a powerful tool, but it’s not magic. The quality of output depends on the quality of input.

Key Insight: Treat Copilot like a junior developer who needs clear instructions.

Time Investment:

  • Learning these techniques: 2-3 hours
  • Applying them: Becomes natural after 1 week
  • ROI: 2-3x improvement in Copilot effectiveness

Master prompt engineering, and Copilot becomes an invaluable coding partner.

My productivity gain: From 25% to 45% with better prompting.