Module 1 · Prompt Foundations · Lesson 2 of 18
Anatomy of a Strong Developer Prompt: Context, Specificity, Constraints and Output Format
Learning outcome
You can take a vague request ("write a method that sums the orders") and rewrite it as a prompt that reliably produces code you would merge: the right platform, the right types, the business rules stated, the constraints explicit, and the answer in a shape you can check. You can name the parts of a strong prompt, say which ones matter most for a given task, and explain why "act as a senior engineer" on its own changes very little.
Intuition
A prompt is a ticket you hand to a fast colleague who has never seen your codebase. A good ticket says what is wanted, why, what the definition of done is, and what must not change. The model is not lazy or stupid when it guesses; it guesses because the ticket left a gap.
A practical formula, kept from the earlier version of this course because it works:
Context + Task + Relevant code + Constraints + Expected output
flowchart TB
C["Context: app type, .NET version, packages, architecture"] --> P["prompt"]
T["Task: one verb, one artifact (fix, write, review, explain)"] --> P
R["Relevant code: real types and signatures, the error"] --> P
K["Constraints: business rules, what must not change, no new packages"] --> P
O["Expected output: files by name, tests, format, length"] --> P
P --> A["answer you can check"]Deep dive
1. Context: the facts the model cannot know
| Tell it | Why it changes the answer |
|---|---|
| App type (Web API, minimal API, worker, Blazor, console, library) | Hosting model, DI, lifetime and error handling differ |
| Target framework and language version (".NET 8, C# 12") | Available APIs (TimeProvider, primary constructors, collection expressions) |
| Data access (EF Core 8, Dapper, raw ADO.NET) and database | Query patterns, transactions, SQL dialect |
| Architecture (layered, Clean Architecture, vertical slices) | Where code goes and which project references which |
| Test stack (xUnit, NSubstitute, Moq, Testcontainers) | What the test code looks like |
Conventions (nullable enabled, warnings as errors, sealed by default) | Small things that otherwise cost review time |
Most IDE assistants let you store this once in a repository instructions file, covered in Lesson 3, so you do not retype it.
2. Task: one verb, one artifact
"Help with orders" is not a task. "Write a method", "find the root cause", "review this diff for X", "explain this code to a new joiner" are tasks. Name the artifact precisely: an ASP.NET Core minimal API endpoint, an EF Core query, an xUnit theory, a Kubernetes probe. When a request has several parts, number them, or better, split them into separate prompts.
3. Specificity: replace adjectives with facts
The weakest words in developer prompts are adjectives: fast, clean, secure, robust, production-ready. Replace each with something checkable:
| Vague | Specific |
|---|---|
| make it fast | p95 under 50 ms for 10,000 rows; no per-row database call |
| make it secure | caller must own the order (tenant id from the token, never from the body) |
| handle errors | invalid input returns 400 problem details; duplicate email returns 409 |
| production-ready | nullable-clean, cancellation tokens passed through, logs contain no PII |
| clean code | match the style of the attached InvoiceService.cs |
4. Constraints: what must not happen
Constraints prevent the most common unwanted "helpfulness":
- "Do not add NuGet packages."
- "Keep the public signature unchanged."
- "Do not change behavior; list anything that would change."
- "Use
decimalfor money andInvariantCulturefor parsing." - "Do not touch files other than X."
Negative constraints work best when you also say what to do instead ("no DateTime.Now; inject TimeProvider").
5. Expected output: make the answer checkable
Ask for the shape you will review:
- code by file name, only changed members, or a unified diff;
- a list of assumptions it made;
- tests that prove the rules;
- a short "what could break" section;
- a length limit for explanations ("at most 5 bullets").
Roles and personas
"Act as a senior .NET engineer" sets tone and level of detail, but it is not a substitute for facts. A role is worth keeping when it changes the lens: "review as an application security engineer" really does steer the answer toward authorization and injection. A role with no context is decoration.
Ask for questions when the gap is real
For bigger tasks add: "If something essential is missing, ask up to 3 questions before writing code; otherwise state your assumptions and continue." This gets you clarifying questions where they matter and avoids a chat that never produces code.
Code example: one request, two prompts
Before
Write a C# method that sums the order totals in a CSV file.
Typical output (shortened):
public static double Total(string[] lines) => lines.Sum(l => double.Parse(l.Split(',')[1]));After
Context: .NET 8 console tool, C# 12, nullable enabled. Runs on build agents in several countries. Task: write a parser and a total for order lines. Input line format: Id,Amount,PlacedAt e.g. A-100,1234.50,2026-10-01T09:30:00+05:30 Rules: - Amount is money: use decimal, never double. Amounts always use '.' as the decimal separator. - PlacedAt has an offset; store it as UTC. - A malformed line throws FormatException that includes the line. Constraints: no NuGet packages, no regex. Output: one file with a record Order, Parse(string) and Total(IEnumerable<string>). Then list your assumptions.
The program below runs both versions on a machine whose culture is German, the situation the first prompt never mentioned:
using System.Globalization;
// The same task, two prompts. "Before" is what a vague prompt typically returns; "After" follows the specific prompt.
var lines = new[] { "A-100,1234.50,2026-10-01T09:30:00+05:30", "A-101,99.99,2026-10-01T23:10:00+05:30" };
CultureInfo.CurrentCulture = new CultureInfo("de-DE"); // a developer laptop or build agent in Germany
Console.WriteLine($"Before (double, current culture): total = {Before.Total(lines)}");
Console.WriteLine($"After (decimal, invariant): total = {After.Total(lines).ToString(CultureInfo.InvariantCulture)}");
Console.WriteLine($"After first order date (UTC): {After.Parse(lines[0]).PlacedAtUtc:O}");
static class Before
{
// Output of: "write a C# method that sums the order totals in a CSV"
public static double Total(string[] lines) => lines.Sum(l => double.Parse(l.Split(',')[1]));
}
static class After
{
// Output of the specific prompt: decimal money, invariant culture, DateTimeOffset kept as UTC, errors name the line.
public sealed record Order(string Id, decimal Amount, DateTimeOffset PlacedAtUtc);
public static Order Parse(string line)
{
var parts = line.Split(',');
if (parts.Length != 3) throw new FormatException($"Expected 3 fields but got {parts.Length}: '{line}'");
try
{
var amount = decimal.Parse(parts[1], NumberStyles.Number, CultureInfo.InvariantCulture);
var placed = DateTimeOffset.Parse(parts[2], CultureInfo.InvariantCulture).ToUniversalTime();
return new Order(parts[0], amount, placed);
}
catch (Exception e) when (e is FormatException or OverflowException or ArgumentException)
{
throw new FormatException($"Invalid order line: '{line}'", e);
}
}
public static decimal Total(IEnumerable<string> lines) => lines.Select(Parse).Sum(o => o.Amount);
}Output:
Before (double, current culture): total = 133449 After (decimal, invariant): total = 1334.49 After first order date (UTC): 2026-10-01T04:00:00.0000000+00:00
The "before" code is not just imprecise, it is wrong by a factor of 100: in de-DE the . is a group separator, so 1234.50 parses as 123450 and 99.99 as 9999. The specific prompt did not make the model smarter. It removed four guesses: the number type, the culture, the time zone and the error behavior.
Failure modes
- Context dumping. Pasting 3,000 lines "for context" dilutes the important part. Paste what the change touches.
- Hidden business rules. The model writes CRUD fast, but "email must be unique" or "premium discount applies at 1000 or more" is only in your head.
- Several tasks in one prompt. The answer does half of each. Split them.
- Adjectives instead of numbers. "Fast" and "secure" get generic advice.
- No output shape. You get a wall of prose and code that is hard to review.
- Role without facts. "Act as an expert" plus "fix this" still forces the model to guess.
Interview angle
"How do you write a good prompt for a coding task?" Answer with the formula and an example: "I give the facts it can't infer: app type, .NET version, packages and the real types. I make one task per prompt, state the business rules and constraints as testable statements, and ask for a checkable output: files by name, assumptions and tests. For example, 'sum the orders' becomes: decimal money, invariant culture, UTC, a FormatException naming the bad line, no packages. That removes the guesses that cause real bugs, like culture-dependent parsing."
Practice questions
- Rewrite "make this endpoint secure" as a specific constraint for a multi-tenant API.
Answer: For example: "The tenant id comes from the tid claim, never from the route or body; every query filters by it; a request for another tenant's order returns 404, not 403, so the order's existence is not leaked."
- Why is
double.Parse(text)without a culture risky in code that runs on many machines?
Answer: It uses the current culture. In cultures where . is a group separator, 1234.50 parses as 123450. Money should be decimal parsed with CultureInfo.InvariantCulture or an explicit culture.
- When is a role ("act as a security reviewer") genuinely useful?
Answer: When it changes the lens of the answer, for example toward authorization and injection risks. It does not replace context such as versions, code and rules.
- What output format would you request for a refactor of a 300-line class?
Answer: Only the changed members or a unified diff, a list of behavior changes (ideally "none"), assumptions, and tests that pin current behavior.
- How do you stop an assistant from asking ten questions before writing anything?
Answer: Allow a small number of questions only when information is essential, and otherwise ask it to state assumptions and continue.
Revision checklist
- Context + Task + Relevant code + Constraints + Expected output.
- Platform facts first: app type, .NET version, packages, architecture.
- One verb, one artifact per prompt.
- Replace adjectives with numbers and rules.
- Say what must not change, and what to do instead.
- Ask for a checkable answer: files, assumptions, tests, risks.
Related on TechInterviewNotes
- Previous lesson: How AI Coding Assistants Read Your Prompt
- Next lesson: Giving the Assistant Code Context
Analogy
A good bug ticket
"It's broken" gets you a shrug. "On build 1.4.2, POST /orders with an empty cart returns 500; expected 400; logs attached" gets you a fix the same day. Prompts are tickets.
Quick reference
Prompt skeleton
- Context: app type, .NET 8 / C# 12, packages, architecture.
- Task: one verb + exact artifact.
- Code: real types, signatures, error text.
- Rules and constraints: testable; what must not change.
- Output: files by name, assumptions, tests, risks; length limit.