GitHub Copilot Prompt Engineering - Getting Better Code Suggestions
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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
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
- Write detailed comments before functions
- Use descriptive names for functions and variables
- Include type hints (Python) or types (TypeScript)
- Show examples in comments
- Import relevant libraries first
- Write tests before implementation
- Structure files with clear context
- Be consistent with naming conventions
- Iterate incrementally for complex functions
- 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.