If you could learn only one refactoring technique, Extract Method should be it. It’s the simplest, most frequently applicable, and most immediately impactful way to improve code. Martin Fowler calls it the backbone of refactoring — and for good reason.

The Principle

When you see a block of code that does something specific, extract it into a well-named method. The method name replaces the need for a comment.

Before and After

// ❌ A 60-line function doing everything
async function processMonthlyBilling(accounts: Account[]) {
  const results: BillingResult[] = [];

  for (const account of accounts) {
    // Calculate base charge
    let charge = 0;
    if (account.plan === 'basic') {
      charge = 9.99;
    } else if (account.plan === 'pro') {
      charge = 29.99;
    } else if (account.plan === 'enterprise') {
      charge = 99.99;
    }

    // Apply usage overage
    const includedStorage = account.plan === 'basic' ? 5 : account.plan === 'pro' ? 50 : 500;
    if (account.storageUsedGB > includedStorage) {
      const overageGB = account.storageUsedGB - includedStorage;
      charge += overageGB * 0.10;
    }

    // Apply discounts
    const accountAge = monthsBetween(account.createdAt, new Date());
    if (accountAge > 24) {
      charge *= 0.9; // 10% loyalty discount
    }
    if (account.referralCount > 5) {
      charge *= 0.95; // 5% referral discount
    }

    // Apply tax
    const taxRate = getTaxRate(account.country);
    const tax = charge * taxRate;
    const total = charge + tax;

    // Create invoice
    const invoice = {
      accountId: account.id,
      subtotal: charge,
      tax,
      total,
      dueDate: addDays(new Date(), 30),
    };

    // Send notification
    await sendEmail(account.email, 'Monthly Invoice', `Your total: $${total.toFixed(2)}`);
    if (total > 100) {
      await sendSlackAlert(`High invoice: ${account.id} — $${total.toFixed(2)}`);
    }

    results.push({ accountId: account.id, invoice, status: 'sent' });
  }

  return results;
}

After extracting methods:

// ✅ Each method tells you WHAT it does — the HOW is one click away
async function processMonthlyBilling(accounts: Account[]): Promise<BillingResult[]> {
  const results: BillingResult[] = [];

  for (const account of accounts) {
    const charge = calculateCharge(account);
    const { tax, total } = applyTax(charge, account.country);
    const invoice = createInvoice(account.id, charge, tax, total);
    await notifyAccount(account, total);

    results.push({ accountId: account.id, invoice, status: 'sent' });
  }

  return results;
}

function calculateCharge(account: Account): number {
  let charge = getBaseCharge(account.plan);
  charge += calculateOverageCharge(account);
  charge = applyDiscounts(charge, account);
  return charge;
}

function getBaseCharge(plan: Plan): number {
  const prices: Record<Plan, number> = {
    basic: 9.99,
    pro: 29.99,
    enterprise: 99.99,
  };
  return prices[plan];
}

function calculateOverageCharge(account: Account): number {
  const included: Record<Plan, number> = { basic: 5, pro: 50, enterprise: 500 };
  const overage = Math.max(0, account.storageUsedGB - included[account.plan]);
  return overage * 0.10;
}

function applyDiscounts(charge: number, account: Account): number {
  let discounted = charge;
  const accountAge = monthsBetween(account.createdAt, new Date());
  
  if (accountAge > 24) discounted *= 0.9;
  if (account.referralCount > 5) discounted *= 0.95;
  
  return discounted;
}

function applyTax(charge: number, country: string) {
  const taxRate = getTaxRate(country);
  const tax = charge * taxRate;
  return { tax, total: charge + tax };
}

function createInvoice(accountId: string, subtotal: number, tax: number, total: number) {
  return { accountId, subtotal, tax, total, dueDate: addDays(new Date(), 30) };
}

async function notifyAccount(account: Account, total: number): Promise<void> {
  await sendEmail(account.email, 'Monthly Invoice', `Your total: $${total.toFixed(2)}`);
  if (total > 100) {
    await sendSlackAlert(`High invoice: ${account.id} — $${total.toFixed(2)}`);
  }
}

When to Extract

  • A comment explains what the next block does → Extract and use the comment as the method name
  • A method is longer than ~15 lines → Look for extraction opportunities
  • You see indentation deeper than 2 levels → The inner block is likely a separate concern
  • Code is reused (or could be) → Extract for reuse
  • A block has its own local variables → It’s already a natural boundary

Naming Is Everything

The extracted method’s name should make the calling code read like prose:

// ❌ Bad names — still need to read the body
function process(data: Data) { /* ... */ }
function doStuff(account: Account) { /* ... */ }
function helper(items: Item[]) { /* ... */ }

// ✅ Good names — the call site is self-documenting
function calculateShippingCost(order: Order): number { /* ... */ }
function isEligibleForDiscount(customer: Customer): boolean { /* ... */ }
function formatCurrencyForDisplay(amount: number, locale: string): string { /* ... */ }

IDE Support

Modern IDEs make Extract Method nearly automatic:

  • VS Code: Select code → Ctrl+Shift+R → “Extract to function”
  • WebStorm: Select code → Ctrl+Alt+M
  • The IDE handles: parameter detection, return values, and scope

Use it dozens of times a day. It’s fast, safe, and immediately improves readability.

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” — Martin Fowler