AI is often better at refactoringWhat is refactoring?Restructuring existing code to make it cleaner, more readable, or more efficient without changing what it does. code than writing it from scratch. When generating, AI makes countless decisions about structure, naming, and patterns. When refactoring, the structure already exists, the problem is constrained, and AI performs better on constrained problems.
This means you can use a two-pass workflow: generate first (fast, messy, functional), then refactor second (targeted, specific, clean).
The two-pass technique
Separate creation from cleanup. Treating them as separate prompts gives AI a clear objective each time.
Pass 1: generate the feature
Focus entirely on functionality. Do not worry about code quality.
Prompt: "Build a React component that displays a sortable, filterable
table of products. Each product has a name, price, category, and
stock status. Include search by name and filter by category."AI generates 150 lines of working code. It has generic names, filtering and sorting logic mixed into the render function, and magic strings everywhere.
Pass 2: refactor
Now give AI the generated code and ask for specific improvements.
Prompt: "Refactor this component. Specifically:
1. Extract the filtering logic into a custom hook called useProductFilters
2. Extract the sorting logic into a separate function
3. Replace the magic strings ('name', 'price', 'category') with a
SORT_FIELDS constant
4. Rename generic variables (data, items, result) to domain-specific names"The key difference: you are the architect making decisions; AI is the developer executing them.
Effective refactoringWhat is refactoring?Restructuring existing code to make it cleaner, more readable, or more efficient without changing what it does. prompts
The quality of AI's refactoring depends entirely on how specific your prompt is.
| Prompt quality | Example | Result |
|---|---|---|
| Too vague | "Make this code better" | AI makes random changes, might break things |
| Slightly better | "Clean up this code" | AI reformats and maybe renames a few things |
| Good | "Extract the validation logic into a separate function" | AI does exactly what you asked |
| Great | "Extract the validation logic into validateOrderItems(items) that returns an array of error messages, or an empty array if valid" | AI produces exactly what you need |
The more constraints you give, the better the output.
"Extract X into a function"
"Extract the price calculation logic (lines 15-30) into a pure function
called calculateItemPrice that takes an item and a discount object,
and returns the final price as a number.""Remove duplication between X and Y"
"These two functions (formatUserAddress and formatCompanyAddress) share
80% of their logic. Extract the common parts into a formatAddress
helper and have both functions call it.""Simplify this conditional"
"This function has four levels of nested if statements. Refactor it
to use early returns (guard clauses) so the maximum nesting depth
is two levels.""Replace magic values with named constants"
"Replace all magic numbers in this file with named constants. Group
them at the top of the file under a CHECKOUT_CONFIG object. Include
TAX_RATE, FREE_SHIPPING_THRESHOLD, and MAX_RETRY_ATTEMPTS."When AI refactoringWhat is refactoring?Restructuring existing code to make it cleaner, more readable, or more efficient without changing what it does. makes things worse
Over-abstraction
You ask AI to "make this code more reusable" and it turns a 20-line function into a 100-line abstract framework.
// You had this
function sendEmail(to, subject, body) {
return emailClient.send({ to, subject, body });
}
// You asked AI to "make it more reusable"
// AI generated this
class MessageDispatcher {
constructor(transport, config = {}) {
this.transport = transport;
this.config = { retries: 3, timeout: 5000, ...config };
this.middleware = [];
}
use(middleware) {
this.middleware.push(middleware);
return this;
}
async dispatch(message) {
let processed = message;
for (const mw of this.middleware) {
processed = await mw(processed);
}
let attempts = 0;
while (attempts < this.config.retries) {
try {
return await this.transport.send(processed);
} catch (e) {
attempts++;
if (attempts >= this.config.retries) throw e;
}
}
}
}The original three-line function was fine. The "reusable" version added retries, middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it., and configuration that nobody asked for.
Premature optimization
// Before: clear and readable
const activeUsers = users.filter(u => u.isActive);
const names = activeUsers.map(u => u.name);
const sorted = names.sort();
// AI "optimized" version
const sorted = users.reduce((acc, u) => {
if (u.isActive) {
const idx = acc.findIndex(n => n > u.name);
acc.splice(idx === -1 ? acc.length : idx, 0, u.name);
}
return acc;
}, []);The "optimized" version does everything in a single pass but is dramatically harder to read. Unless you are processing millions of users, the three-line version is correct.
Behavior changes
The most dangerous failure mode: AI changes what the code does while refactoring how it does it.
// Original: returns empty array when user has no orders
function getUserOrders(userId) {
const user = users.find(u => u.id === userId);
if (!user) return [];
return user.orders || [];
}
// AI "refactored" version: throws when user not found
function getUserOrders(userId) {
const user = users.find(u => u.id === userId);
if (!user) throw new Error(`User ${userId} not found`);
return user.orders;
}The AI version is arguably "better," but it is a behavior change. Every caller that expected an empty array will now crash.
Reviewing AI refactoringWhat is refactoring?Restructuring existing code to make it cleaner, more readable, or more efficient without changing what it does.
| Check | Question | How to verify |
|---|---|---|
| Behavior preservation | Does the refactored code produce the same output for the same inputs? | Test with known inputs, especially edge cases |
| Error handling | Are errors still handled the same way? | Check null, undefined, empty arrays, network failures |
| Return values | Are return types and shapes unchanged? | Compare the function signatures |
| Side effects | Are the same side effects happening in the same order? | Trace through API calls, state mutations |
| Edge cases | Do boundary conditions still work? | Test with empty inputs, max values, special characters |
A refactoringWhat is refactoring?Restructuring existing code to make it cleaner, more readable, or more efficient without changing what it does. workflow
Step 1: generate and review
Ask AI to build the feature. Accept messy code. Then read it and note the problems: bad names, long functions, duplication, magic values.
Step 2: refactor with specific prompts
For each problem, give AI a specific instruction. Do them one at a time, smaller changes are easier to verify.
Prompt 1: "Rename 'data' to 'productCatalog' and 'items' to
'cartItems' throughout this file."
Prompt 2: "Extract the tax calculation (lines 45-52) into a
function called calculateSalesTax(subtotal, stateCode)."
Prompt 3: "Replace the hardcoded discount thresholds with a
DISCOUNT_TIERS constant at the top of the file."Step 3: review again
After each refactoring step, verify that behavior is preserved. If something breaks, you know exactly which change caused it.
When not to use AI for refactoringWhat is refactoring?Restructuring existing code to make it cleaner, more readable, or more efficient without changing what it does.
Moving files and updating imports is better done with your editor's refactoring tools. Renaming a widely-used function is safer with "Rename Symbol" (F2), because the editor understands scopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it.. Deleting dead codeWhat is dead code?Code that exists in the project but is never executed or referenced, adding confusion without serving a purpose. should be done by hand, because you need to verify the code is actually unused.
The sweet spot for AI refactoring is within a single file or small group of related files, on logic you understand well enough to verify.
Building the habit
The best time to refactor is right after the feature works, the code is fresh in your mind and the cost is lowest. Build a simple rule: for every hour of AI-assisted development, spend 15 minutes refactoringWhat is refactoring?Restructuring existing code to make it cleaner, more readable, or more efficient without changing what it does.. The goal is not perfect code. The goal is code you can change with confidence next time.