Prompt Engineering for Developer Productivity
Practical prompt engineering techniques that turn LLMs into reliable coding assistants — covering context management, chain-of-thought, and structured outputs.

Large language models are useful for coding tasks, but their output quality varies wildly based on how you ask. Vague prompts produce vague answers. Precise prompts with context, constraints, and examples produce code you can actually use. The difference is not the model — it is the prompt.
This is not about tricks or jailbreaks. It is about structuring your requests so the model has enough information to give useful answers on the first try.
The Anatomy of a Good Prompt
Every effective coding prompt has four components: context, task, constraints, and format.
# ❌ Vague prompt — produces generic, possibly wrong code
"Write a function to validate emails"
# ✅ Structured prompt — produces usable, specific code
Context: TypeScript project using Zod for validation.
The function is part of a user registration form handler.
Task: Write a function that validates an email address.
Constraints:
- Must use Zod schema validation
- Must check for disposable email domains (mailinator, tempmail, guerrillamail)
- Must return a typed result object, not throw errors
- Must handle null/undefined input
Format: TypeScript function with JSDoc, followed by unit test examples.The structured version tells the model exactly what stack to use, what edge cases to handle, and what the output should look like. The vague version forces the model to guess at every decision.
Context Window Management
Models have limited context. Dumping your entire codebase into the prompt wastes tokens on irrelevant code. Instead, provide targeted context — the specific types, interfaces, and patterns the generated code must integrate with.
// Include only the interfaces the generated code needs to implement
// Instead of pasting 500 lines of code, paste 30 lines of types
/*
Given these existing types and the database schema:
interface User {
id: string;
email: string;
role: 'admin' | 'user' | 'moderator';
createdAt: Date;
}
interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
pageSize: number;
hasMore: boolean;
}
Database: PostgreSQL with Prisma ORM
Table: users (id UUID PK, email VARCHAR UNIQUE, role VARCHAR, created_at TIMESTAMP)
Write a function that queries users with pagination, filtering by role,
and returns a PaginatedResponse<User>.
*/// The model can now produce code that matches your exact types
async function getUsers(
params: { role?: User['role']; page: number; pageSize: number }
): Promise<PaginatedResponse<User>> {
const { role, page, pageSize } = params;
const skip = (page - 1) * pageSize;
const where = role ? { role } : {};
const [data, total] = await Promise.all([
prisma.user.findMany({ where, skip, take: pageSize, orderBy: { createdAt: 'desc' } }),
prisma.user.count({ where }),
]);
return {
data,
total,
page,
pageSize,
hasMore: skip + data.length < total,
};
}Chain-of-Thought for Complex Logic
For multi-step algorithms or architectural decisions, ask the model to reason through the problem before writing code.
# ❌ Direct request — model jumps to code, may miss edge cases
"Implement a rate limiter using the sliding window algorithm"
# ✅ Chain-of-thought prompt
Task: Implement a sliding window rate limiter.
Before writing code, explain:
1. How the sliding window algorithm differs from fixed window
2. What data structure you'll use to track request timestamps
3. How you'll handle the window boundary calculation
4. What happens when the limit is exceeded
Then implement in TypeScript with:
- Redis for storage (using ioredis)
- Configurable window size and request limit
- Return type that includes remaining requests and reset time// The model's reasoning leads to more robust implementations
// because it explicitly considers edge cases before coding
interface RateLimitResult {
allowed: boolean;
remaining: number;
resetAt: Date;
retryAfter?: number; // seconds until next allowed request
}
async function checkRateLimit(
redis: Redis,
key: string,
limit: number,
windowMs: number
): Promise<RateLimitResult> {
const now = Date.now();
const windowStart = now - windowMs;
// Remove entries outside the current window
// Count entries within the window
// Add current request if under limit
const pipeline = redis.pipeline();
pipeline.zremrangebyscore(key, 0, windowStart);
pipeline.zcard(key);
pipeline.zadd(key, now.toString(), `${now}-${Math.random()}`);
pipeline.pexpire(key, windowMs);
const results = await pipeline.exec();
const currentCount = results?.[1]?.[1] as number;
if (currentCount >= limit) {
// Over limit — remove the entry we just added
await redis.zremrangebyscore(key, now, now);
const oldestInWindow = await redis.zrange(key, 0, 0, 'WITHSCORES');
const resetAt = oldestInWindow.length > 1
? parseInt(oldestInWindow[1]) + windowMs
: now + windowMs;
return {
allowed: false,
remaining: 0,
resetAt: new Date(resetAt),
retryAfter: Math.ceil((resetAt - now) / 1000),
};
}
return {
allowed: true,
remaining: limit - currentCount - 1,
resetAt: new Date(now + windowMs),
};
}Few-Shot Examples
When the model needs to follow a specific pattern, provide examples of the input-output mapping. Two or three examples establish the pattern more reliably than lengthy descriptions.
Task: Generate TypeScript API route handlers following this project's pattern.
Example 1:
Input: GET /api/users — list all users with pagination
Output:// app/api/users/route.ts
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '20');
const users = await db.user.findMany({
skip: (page - 1) * limit,
take: limit,
});
return NextResponse.json({ data: users, page, limit });
}Example 2:
Input: POST /api/users — create a new user
Output:// app/api/users/route.ts
export async function POST(request: NextRequest) {
const body = await request.json();
const parsed = createUserSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ error: parsed.error.flatten() },
{ status: 400 }
);
}
const user = await db.user.create({ data: parsed.data });
return NextResponse.json({ data: user }, { status: 201 });
}Now generate:
Input: DELETE /api/users/[id] — delete a user by IDThe model now understands your project's conventions: Next.js route handlers, Zod validation, Prisma ORM, consistent response format. It follows the pattern without you specifying each decision.
Structured Output Requests
When you need the model to output data in a specific format — not just code — be explicit about the structure.
# ❌ Ambiguous output format
"What are the security issues in this code?"
# ✅ Structured output format
Analyze this code for security vulnerabilities.
Output format (JSON):
{
"vulnerabilities": [
{
"severity": "critical" | "high" | "medium" | "low",
"line": number,
"issue": "brief description",
"fix": "code snippet showing the fix",
"cwe": "CWE-XXX reference"
}
],
"summary": "one paragraph overall assessment"
}// Model output follows the exact schema
{
"vulnerabilities": [
{
"severity": "critical",
"line": 15,
"issue": "SQL query built with string concatenation allows injection",
"fix": "db.query('SELECT * FROM users WHERE id = $1', [userId])",
"cwe": "CWE-89"
},
{
"severity": "high",
"line": 28,
"issue": "User input rendered without sanitization enables XSS",
"fix": "import DOMPurify from 'dompurify'; element.innerHTML = DOMPurify.sanitize(input);",
"cwe": "CWE-79"
}
],
"summary": "Two vulnerabilities found: a critical SQL injection and a high-severity XSS. Both stem from unsanitized user input being used directly in queries and DOM manipulation."
}Iterative Refinement
Single-shot prompts rarely produce production-ready code. Treat the model as a pair programmer — give feedback, ask for modifications, narrow in on the right solution.
# Round 1: Get the basic structure
"Implement a retry mechanism with exponential backoff for HTTP requests"
# Round 2: Add constraints based on the output
"Good. Now modify it to:
- Use a jitter factor to prevent thundering herd
- Accept a custom shouldRetry predicate
- Log each retry attempt with the attempt number and delay
- Respect a maximum total timeout, not just max retries"
# Round 3: Integration
"Now wrap this in a class that can be used as middleware
for our existing HttpClient. Show usage examples."Each round builds on the previous output. The model has the full conversation context, so it refines rather than starts over. This is faster than trying to specify everything perfectly in one shot.
Prompt Templates for Recurring Tasks
For tasks you perform repeatedly, save prompt templates that include your project's conventions.
## Bug Fix Template
Context: [paste the error message and stack trace]
Relevant code: [paste the function/file where the error occurs]
Expected behavior: [what should happen]
Actual behavior: [what actually happens]
Constraints:
- Fix must not change the public API
- Must include a test case that would have caught this bug
- Explain why the bug occurred (root cause)
## Code Review Template
Review this code for:
1. Logic errors or edge cases
2. Performance issues (N+1 queries, unnecessary re-renders)
3. Security vulnerabilities
4. Deviation from project conventions
For each issue found, provide:
- Line number
- Issue description
- Suggested fix with codeThese templates turn LLMs from inconsistent chat tools into reliable development assistants. The consistency comes from the prompt structure, not from the model's training.
Key Takeaways
- Structure prompts with context, task, constraints, and format — vague prompts produce vague code
- Provide targeted context, not entire files — types and interfaces give the model what it needs to integrate with your codebase
- Use chain-of-thought for complex logic — reasoning before coding catches edge cases
- Few-shot examples establish patterns — two examples are worth more than a paragraph of description
- Iterate in rounds — treat the model as a pair programmer, not an oracle
- Save prompt templates — recurring tasks get consistent results with reusable structures


