When you ask an assistant to “add feature X”, you often get a rewrite.
It’s not malicious. It’s just the path of least resistance: the model sees a large surface area of code and responds with a large surface area of output.
In real codebases, rewrites are expensive:
- reviewers can’t tell what actually changed
- subtle behavior gets lost
- merge conflicts explode
- tests become the archaeology department
Diff-first prompting is a simple way to keep changes small, reviewable, and safe.
Instead of asking for “the new file”, you ask for a patch.
What “diff-first” means
Diff-first prompting is an interaction pattern where the primary output is a unified diff (or a list of file-by-file edits), not a fresh copy of the final code.
You’re telling the assistant:
- Don’t rewrite. Make the smallest change that works.
- Show me exactly what changed. In a format I can review.
- Respect constraints. Keep style, architecture, and existing behavior intact.
You can use git-style diffs, or a structured “edit plan” with exact file/line edits. I prefer diffs because they are:
- easy to eyeball
- easy to apply
- easy to revert
The moment diff-first shines
Any time you’re changing code that already works.
Examples:
- adding one validation rule
- extending an API response with one field
- fixing a flaky test
- swapping one dependency function
- adding logging/metrics
If you want a redesign, don’t use this. Diff-first is for surgical edits.
The 4-step workflow
Here’s a repeatable loop that keeps models honest.
1) Provide the context and the boundary
Give only the relevant files (or excerpts) and state what must not change.
Bad boundary: “don’t break anything”.
Good boundary:
- “Do not change public function signatures.”
- “Do not alter database schema.”
- “Keep existing error messages unchanged.”
2) Ask for an edit plan first
Before generating a patch, ask for a short plan: which files, what edits, why.
This prevents the classic problem: “I changed 6 files because… vibes.”
3) Require a unified diff as the final output
Force the assistant to commit to minimality.
4) Add an acceptance checklist
Turn “looks good” into something you can verify:
- tests updated/added
- edge cases considered
- backward compatibility maintained
A prompt template you can reuse
Paste this into your next change request:
You are helping me make a small, safe change to an existing codebase.
Rules:
- Do NOT rewrite files.
- Prefer the smallest possible change.
- Keep public APIs stable unless I explicitly ask.
- Output format:
1) Edit plan (max 8 bullets)
2) Unified diff (git-style) ONLY
Task:
<what you want>
Constraints:
<what must not change>
Acceptance criteria:
- <bullet>
- <bullet>
Context:
<relevant files or snippets>
That’s it. The magic is in the “unified diff ONLY” line.
Concrete example: add request IDs to logs (Node/Express)
Let’s say you have an Express app and you want every log line to include a request ID, without refactoring your logger.
Task:
- generate a request ID per request
- attach it to
req - include it in logs from route handlers
Context (simplified):
// app.js
import express from "express";
import { logger } from "./logger.js";
const app = express();
app.get("/health", (req, res) => {
logger.info("health check");
res.json({ ok: true });
});
export { app };
A non-diff-first prompt might return a brand new app structure.
A diff-first prompt pushes it toward the smallest workable change. For example:
- add a middleware that sets
req.requestId - update log calls to include
{ requestId: req.requestId }
What you want back is something like:
diff --git a/app.js b/app.js
index 1111111..2222222 100644
--- a/app.js
+++ b/app.js
@@ -1,12 +1,26 @@
import express from "express";
import { logger } from "./logger.js";
+import crypto from "crypto";
const app = express();
+
+app.use((req, res, next) => {
+ req.requestId = crypto.randomUUID();
+ next();
+});
app.get("/health", (req, res) => {
- logger.info("health check");
+ logger.info("health check", { requestId: req.requestId });
res.json({ ok: true });
});
export { app };
That patch is reviewable in seconds.
Why this reduces risk
Diff-first prompting improves reliability for a boring reason: it shrinks the solution space.
When the assistant has to produce a patch:
- it’s harder to “accidentally” redesign your architecture
- it’s easier to spot incorrect imports, typos, or behavior changes
- it nudges the model toward local reasoning (“what is the smallest edit?”)
It also changes your review process. You’re not reading an essay; you’re scanning a change.
Common failure modes (and how to prevent them)
1) The diff doesn’t apply
Fix: include file paths and enough surrounding lines. If you’re working from snippets, tell the assistant to include an “approximate diff” and a note about where it goes.
2) The patch is small but wrong
Fix: add a tiny test requirement in acceptance criteria. Even one test forces the assistant to think about behavior.
3) The assistant edits unrelated files
Fix: add a hard constraint: “Only modify these files: …”.
4) “Unified diff ONLY” gets ignored
Fix: be strict. If it outputs anything else, reply with:
Regenerate. Output the unified diff only.
You’re training the interaction, not the model.
My go-to diff-first checklist
Before I apply a patch, I quickly verify:
- Does it touch the minimum number of files?
- Are public signatures unchanged?
- Are error cases handled?
- Is there a test (or at least a clear manual check)?
- Would I be comfortable reviewing this in a PR?
If the answer is “no”, I ask for a smaller diff.
Wrap-up
If you’re using an assistant for coding, the biggest productivity boost isn’t “more output”. It’s more controlled output.
Diff-first prompting is one of those tiny habits that pays rent immediately:
- smaller changes
- faster reviews
- fewer regressions
- less rewrite fatigue
Try it on your next bugfix and watch the conversation get calmer.
If you already have a favorite prompt pattern for safe edits, I’d love to hear it in the comments — I’m always collecting workflows.
Top comments (0)