Claude Code/
Lesson

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.

02

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 qualityExampleResult
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."
03

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.

AI pitfall
Avoid prompts like "make this more reusable" or "make this production-ready." These are vague enough that AI will invent requirements. Be specific: "add retry logic with 3 attempts" or "extract the email sending into a service class."

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.

04

Reviewing AI refactoringWhat is refactoring?Restructuring existing code to make it cleaner, more readable, or more efficient without changing what it does.

CheckQuestionHow to verify
Behavior preservationDoes the refactored code produce the same output for the same inputs?Test with known inputs, especially edge cases
Error handlingAre errors still handled the same way?Check null, undefined, empty arrays, network failures
Return valuesAre return types and shapes unchanged?Compare the function signatures
Side effectsAre the same side effects happening in the same order?Trace through API calls, state mutations
Edge casesDo boundary conditions still work?Test with empty inputs, max values, special characters
05

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.

AI pitfall
Resist the urge to ask AI to "refactor this entire file." Three small, verified refactorings are safer than one large one. Each step should be small enough that you can confidently say "yes, this is correct" after reviewing it.
06

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.

07

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.