How to use GitHub Copilot for coding
# How to Use GitHub Copilot for Coding (Without It Writing Garbage)
I spent three hours last week debugging a function that GitHub Copilot wrote for me. The code compiled fine. The logic looked correct. But it was subtly wrong in a way that only manifested in production under edge-case load. That's the thing about Copilot: it's brilliant at producing plausible-looking code, and terrifying when you trust it blindly.
After using Copilot daily for six months across Python, TypeScript, and Go projects, here's what I've learned about making it genuinely useful instead of a liability.
## The Setup That Actually Works
First, let's get the basics right. I use Copilot in VS Code, but the patterns apply to JetBrains, Neovim, and others.
**Install and authenticate:**
```
1. Install GitHub Copilot extension
2. Sign in with GitHub account (requires subscription, $10/month or free with student/OSS)
3. Set up keyboard shortcuts: Tab to accept, Ctrl+Enter for suggestions panel
```
**Critical config change** most tutorials skip: I disable Copilot's automatic suggestion in comments and strings. Here's why: when you type `// TODO: implement sorting algorithm`, Copilot will often fill in a bubble sort instead of waiting for context. That noise wastes time.
In VS Code settings.json:
```json
{
"github.copilot.enable": {
"*": true,
"plaintext": false,
"markdown": false
}
}
```
## The 80/20 Rule: Where Copilot Shines
After hundreds of sessions, I've found Copilot excels at exactly three things:
### 1. Boilerplate that follows clear patterns
Writing CRUD endpoints? Copilot nails these because they're formulaic. Here's what I type:
```python
# app/routes/users.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")
def list_users(
```
At this point, Copilot suggests the entire function body: database query, pagination, error handling. I accept it 90% of the time because the pattern is predictable.
**The trap**: Copilot will happily generate the same boilerplate with subtle inconsistencies. I once got two endpoints where one used `offset` pagination and the other used `cursor` pagination, because Copilot learned from different examples in my codebase. Always check parameter names match.
### 2. Regex and string manipulation
I cannot write regex from memory. Copilot can, and it's usually correct for common patterns:
```python
def extract_email_addresses(text: str) -> list[str]:
```
Copilot produces:
```python
import re
pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
return re.findall(pattern, text)
```
That regex is standard and works. But when I asked Copilot for "regex to match valid IPv6 addresses," it generated something that passed basic tests but failed on compressed notation like `::1`. Always test edge cases.
### 3. Tests for existing code
This is Copilot's killer feature. After writing a function, I type:
```python
def calculate_discount(price: float, tier: str) -> float:
if tier == "gold":
return price * 0.8
elif tier == "silver":
return price * 0.9
return price
# Test:
```
Then Copilot suggests:
```python
def test_calculate_discount():
assert calculate_discount(100, "gold") == 80.0
assert calculate_discount(100, "silver") == 90.0
assert calculate_discount(100, "bronze") == 100.0
assert calculate_discount(0, "gold") == 0.0
```
I accept this, then add edge cases Copilot missed: negative prices, unknown tiers, float precision.
## The Prompt Engineering Nobody Talks About
Copilot isn't magic—it's a language model that responds to context. Here's how to steer it:
**Bad prompt:**
```python
# function to sort data
def sort_data(data):
```
Copilot might generate a bubble sort or use `sorted()` with no custom key.
**Good prompt:**
```python
# Sort list of user dicts by last_name, then first_name, case-insensitive
def sort_users(users: list[dict]) -> list[dict]:
```
Now Copilot generates:
```python
return sorted(users, key=lambda u: (u['last_name'].lower(), u['first_name'].lower()))
```
The difference is specificity. I learned to write comments like I'm explaining to a junior developer who's eager but inexperienced.
## When Copilot Lies (And How to Catch It)
Here's the most dangerous thing Copilot does: it invents APIs that don't exist.
Example from last week. I was working with Python's `pathlib`:
```python
from pathlib import Path
p = Path("/tmp/data")
p.
```
Copilot suggested `p.listdir()` which doesn't exist. The correct method is `p.iterdir()`. But `listdir` looks plausible because it exists in `os.listdir`. Copilot conflated the two.
**My survival checklist:**
1. **Never accept without reading** – This sounds obvious, but when you're in flow, it's tempting to tab-tab-tab through suggestions. I force myself to read every line of generated code.
2. **Test with real data immediately** – After accepting a suggestion, I run it against actual inputs before moving on.
3. **Watch for hallucinated imports** – Copilot sometimes imports modules that don't exist or uses deprecated functions. I now check imports first.
4. **Be suspicious of "clever" code** – If Copilot suggests a one-liner with nested comprehensions and mutable defaults, it's probably wrong. I've caught `defaultdict(lambda: [])` used in places where a simple dict would do, causing subtle mutation bugs.
## Real Workflow: Building a CLI Tool
Let me walk through a real session. I'm building a CLI tool to process CSV files.
**Step 1: Skeleton**
I type:
```python
import click
import csv
from pathlib import Path
@click.command()
@click.argument("input_file", type=click.Path(exists=True))
@click.option("--output", "-o", default="output.csv")
def process_csv(input_file, output):
```
Copilot suggests the function body:
```python
"""Process CSV file and write results."""
data = []
with open(input_file, "r") as f:
reader = csv.DictReader(f)
for row in reader:
data.append(row)
# Process data
processed = []
for row in data:
# Add processing logic
processed.append(row)
with open(output, "w") as f:
writer = csv.DictWriter(f, fieldnames=processed[0].keys())
writer.writeheader()
writer.writerows(processed)
```
I accept this skeleton. It's correct but empty. Now I fill in the actual processing.
**Step 2: Specific logic**
I add a comment:
```python
for row in data:
# Convert 'price' column from string to float, handle missing values
```
Copilot suggests:
```python
try:
row["price"] = float(row.get("price", "0.0"))
except ValueError:
row["price"] = 0.0
```
This works for 90% of cases. I manually add handling for `$` prefixes and European decimal commas.
**Step 3: Edge cases**
I write a test file:
```python
def test_process_csv():
# Test with empty file
# Test with missing columns
# Test with malformed data
```
Copilot generates basic tests. I add one it missed:
```python
# Test with BOM character in file
with open("test_bom.csv", "w", encoding="utf-8-sig") as f:
f.write("\ufeffname,price\nitem,10.0")
```
## The Hard Truth About Productivity
After six months, my actual productivity gain is about 30-40% for boilerplate, and 0% for novel algorithms. Copilot is useless for:
- System architecture decisions
- Performance optimization
- Security-critical code (it generates SQL injection vulnerabilities if you're not careful)
- Anything requiring deep domain knowledge
The worst mistake I made was letting Copilot generate code I didn't understand. I had a function that used `itertools.groupby` in a way I couldn't explain. It passed tests. It worked. Then a year later, a bug surfaced because `groupby` requires sorted input, and the data wasn't always sorted.
## Practical Next Step
Instead of trying to learn all of Copilot's features at once, do this: For the next week, use Copilot only for writing tests and docstrings. That's where it adds the most value with the least risk. After you get comfortable reading its output critically, start using it for implementation code.
And always, always run your tests before committing. Copilot will write code that looks right but fails on the first edge case you throw at it. I learned that the hard way, three hours of debugging I'll never get back.