Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
This guide assumes you already know the basics: you’ve installed Copilot, understand tab-to-accept, and you’ve seen inline completions in action. Now it’s time to take one level up. We’ll explore techniques that transform Copilot from a simple autocomplete tool into a usefule pair programming partner. We will be looking at some code examples to demonstrate the features. Clone the following git repository and open in any copilot supported IDE.
git clone git@github.com:anjithp/ai-code-assistant-demo.git
The single most important factor in getting quality suggestions from Copilot isn’t your prompts: it’s your context. Copilot may process all open files in your IDE to understand your codebase patterns.
What this means in practice:
When working on a feature, open all relevant files. For example, If you’re building a new React component that fetches tasks from an API, open:
What to close:
Close files that aren’t relevant to your current task. If you have 20 tabs open from yesterday’s debugging session, Copilot’s attention is diluted across irrelevant context. Each open file consumes Copilot’s limited context window.
Example: Building a task service
Let’s say you need to create a new service method in our example project. Here’s how context changes the outcome:
Poor context (only taskService.ts open):
// Copilot may suggest generic CRUD code
export const getTaskById = async (id: number) => {
// Generic suggestion without your patterns
}
Rich context (open taskService.ts, Task.ts model, Category.ts model, and existing similar service):
// Copilot suggests code matching your exact patterns
export const getTaskById = async (id: number) => {
return await Task.findByPk(id, {
include: [
{
model: Category,
as: 'category',
attributes: ['id', 'name', 'color']
}
]
});
};
The second suggestion matches your project’s Sequelize patterns, includes the relationship you always load, and follows your naming conventions: all because Copilot had the right context.
After context, the next most important thing to get good results is prompts. The best prompts follow the 3S Principle: Specific, Simple, Short.
Specific: Tell Copilot exactly what you need. Include precise details like desired output format, constraints, or examples. This guides Copilot toward relevant suggestions rather than generic ones.
❌ Bad:
Create a hook
✅ Good:
Custom React hook to fetch and manage tasks with loading and error states. Returns tasks array, loading boolean, error string, and CRUD methods
Simple: Break complex tasks into smaller steps. Use straightforward language without unnecessary jargon or complexity. Focus on the core intent to make it easy for the AI to understand and respond.
Instead of: “Create a complete authentication system with JWT, refresh tokens, and role-based access control”
Break it down:
Step 1: Create JWT token generation function
Step 2: Create token verification middleware
Step 3: Create refresh token rotation logic
Short: Keep prompts concise to maintain focus: aim for brevity while covering essentials, as longer prompts can dilute the copilot’s attention.
❌ Too verbose:
This function should take a task object and update it in the database
but first it needs to validate the task data and make sure all the fields are correct and if anything is wrong it should throw an error...
✅ Concise:
// Validates and updates task, throws on invalid data
export const updateTask = async (id: number, data: Partial<TaskData>) => {
In summary, keep the prompts as specific to the task in hand, break down when necessary and be concise to the point.
Write detailed comments above function signatures
Comments directly above where you’re writing code have the strongest influence on Copilot’s suggestions. A well-written comment acts as a specification. It tells Copilot not just what the function does, but how it should behave, what it should return, and any important implementation details.
// Retrieves a single task by ID with associated category
// Returns null if task doesn't exist
// Includes category with id, name, color fields only
export const getTaskById = async (id: number) => {
return await Task.findByPk(id, {
include: [
{
model: Category,
as: 'category',
attributes: ['id', 'name', 'color']
}
]
});
};
Use inline examples to establish patterns
One of the most effective prompting techniques is showing an example, then letting it generate similar code. This is particularly useful when you’re writing repetitive code with slight variations like filter conditions, validation rules, or similar data transformations.
Write the first example manually, add a comment indicating you want more like it, and Copilot will follow the pattern.
// Example: status filter
if (filters.status) {
where.status = filters.status;
}
// Now generate similar code for priority, categoryId
if (filters.priority) {
where.priority = filters.priority; // Copilot follows the pattern
}
if (filters.categoryId) {
where.categoryId = filters.categoryId;
}
Write test descriptions first in TDD
This could be a good trick if you follow TDD in your development workflow. Test-Driven Development works really well with Copilot. When you write your test first, describing what the function should do and what you expect, Copilot can then generate an implementation that satisfies that specification.
The test acts as both a specification and a validation. Copilot sees what behavior you’re testing for and suggests code that produces the expected results.
describe('getTaskStatistics', () => {
it('should return correct task counts by status', async () => {
// Arrange: Create 4 tasks (2 pending, 1 in progress, 1 completed)
// Act: Call getTaskStatistics()
// Assert: Verify counts match
});
});
// Now type the implementation. Copilot will suggest code that satisfies this test
Use Inline Completions when:
Use Copilot Chat when:
Use @workspace for codebase-wide questions
The @workspace participant tells Copilot to search your entire codebase to answer a question. This is incredibly useful when you’re trying to understand how something works across your project, find where a pattern is used, or locate specific functionality. Instead of using grep or manually searching, ask Copilot to find and explain patterns for you.
Use /explain before /fix when debugging
When you encounter a bug, the temptation is to immediately ask Copilot to fix it. However, using /explain first helps you understand the root cause, which leads to better fixes and helps you learn from the issue.
Powerful Chat Features:
Slash commands are shortcuts to common tasks:
/explain – Get a breakdown of complex code/fix – Debug and fix errors/tests – Generate test cases/doc – Create documentationChat participants give Copilot specific context:
@workspace – Search across your entire workspace#file – Reference specific files: "Update #taskService.ts to use async/await"#codebase – Let Copilot search for the right files automaticallyExample chat prompts:
@workspace how do we handle authentication in this codebase?
Show me where JWT tokens are verified.
/explain why is this causing an infinite re-render?
[After understanding the issue]
/fix update the dependency array to prevent re-renders
Essential shortcuts (VS Code)
Tab : Accept suggestionEsc : Dismiss suggestionCtrl+Enter (Windows/Linux) / Cmd+Enter (Mac) : Open Copilot ChatAlt+] : Next suggestionAlt+[ : Previous suggestionCtrl+→ : Accept next word of suggestionMultiple conversation threads
You can have multiple ongoing conversations by clicking the + sign in the chat interface. Use this to:
Quick accept/reject pattern
When a suggestion is 70-80% correct, it’s often faster to accept it and make small edits than to reject it and prompt again. This iterative approach is faster and more productive than waiting for perfect suggestions.
Tab, then editEsc and add clarifying commentAlt+] to see alternativesBuild a personal library of effective prompts
As you work with Copilot, you’ll discover prompts that consistently produce good results for your codebase. Keep a document with these prompts so you can reuse them. This library becomes more valuable over time as you refine prompts for your specific patterns and needs.
Custom instructions let you teach Copilot your preferences and coding standards. Project-level instructions should be saved in the file .github/copilot-instructions.md. This file acts as a project-wide instruction manual that Copilot reads automatically. It’s where you document your tech stack, coding patterns, testing conventions, and any project-specific rules. Think of it as onboarding documentation for Copilot.
Tip: For existing projects, you can put copilot in agent mode, ask it to generate initial instructions file by scanning the repo and make necessary modifications manually.
Example:
# Project Instructions
## Tech Stack
- Backend: Express.js + TypeScript + Sequelize + SQLite
- Frontend: React 19 + TypeScript + Vite
## Code Patterns
- Use functional programming style for services
- All async functions use async/await (never callbacks)
- Services contain business logic, controllers handle HTTP only
- Always include JSDoc comments for exported functions
- Use explicit return types in TypeScript
## Testing
- Tests in `tests/` directory mirror `src/` structure
- Use descriptive test names: "should return 404 when task not found"
- Mock database calls with jest.mock()
## Error Handling
- Controllers throw ApiError for HTTP errors
- Services throw Error with descriptive messages
- Validation errors should specify which field failed
The biggest mistake is accepting code you don’t understand. Every accepted suggestion should pass this test: “Could I have written this myself given time?” If the answer is no, you’re accumulating technical debt or worse critical production incident. In my personal experience, AI assistants have generated buggy and unsafe code several times. Though this is improving you should still be the ultimate judge of the overall quality.
When to write code yourself:
SQL injection is one of the most common and dangerous security vulnerabilities. While modern ORMs like Sequelize protect you by default, Copilot might occasionally suggest raw queries or string concatenation. Always verify that database queries use parameterized inputs, never string interpolation.
// ❌ Dangerous - SQL injection vulnerability
const tasks = await sequelize.query(
`SELECT * FROM tasks WHERE status = '${status}'`
);
// ✅ Safe - parameterized query
const tasks = await Task.findAll({
where: { status }
});
User input should always be validated before being used in business logic or database operations. Copilot may not always add comprehensive validation, so check that suggested code validates required fields, data types, string lengths, and formats. Missing validation can lead to data corruption, application crashes, or security issues.
export const validateTaskData = (data: Partial<TaskCreationAttributes>) => {
// Make sure Copilot added proper validation
if (data.title !== undefined) {
if (data.title.trim().length < 3) {
throw new Error('Title must be at least 3 characters long');
}
}
// Check that all required validations are present
};
HTTP endpoints should have try-catch blocks(or common error handlers) to handle errors gracefully and return appropriate HTTP status codes. Copilot sometimes generates the happy path without error handling, so always verify that exceptions are caught and handled. Unhandled exceptions crash your server or return 500 errors without useful information.
export const createTask = async (req: Request, res: Response) => {
try { // Verify Copilot added error handling
const task = await taskService.createTask(req.body);
res.status(201).json({ success: true, data: task });
} catch (error) {
// Proper error handling should be here
}
};
Hardcoded secrets in source code are a critical security vulnerability. API keys, database passwords, and JWT secrets must come from environment variables, never be written directly in code. Copilot might suggest hardcoded values for convenience so always replace them with environment variable references.
// ❌ Never accept hardcoded secrets
const secret = 'abc123...';
// ✅ Always use environment variables
const secret = process.env.JWT_SECRET;
if (!secret) {
throw new Error('JWT_SECRET not configured');
}
Too many irrelevant files:
Close files from previous tasks. Copilot’s context window is limited so better to have only relevant files open.
Not enough context:
Open related files even if you’re not editing them. That type definition file, that similar component, they all help to get quality suggestions.
Ignoring project patterns:
If you have a unique architecture or patterns, document them in .github/copilot-instructions.md. Don’t expect Copilot to guess.
Copilot is a powerful tool, but you’re still the developer and should have the final say about the code going into production. If you remember the following tips you will go a long way in getting the most value of copilot or any other AI coding assistant.