Technical Guide

Refactoring Techniques: The 10 Most Useful Patterns with Examples

The 10 most practical refactoring techniques with before-and-after examples: extract method, extract class, move method, rename, and more.

Code before and after showing extract method and rename refactoring patterns

In this article:

Refactoring techniques are named, repeatable transformations that change code structure without changing behavior. Using named patterns instead of ad-hoc rewrites has two advantages: the transformation is well-understood and predictable, and IDEs can automate many of them, removing the risk of introducing errors through manual text editing.

This guide covers the 10 refactoring patterns that provide the most value in real-world codebases. Each includes a brief explanation, a before-and-after example, and notes on when to apply it. The selection prioritizes patterns that appear in legacy code cleanup and ongoing maintenance rather than patterns that are theoretically interesting.


Why These 10 Patterns

Fowler’s original refactoring catalog contains over 60 patterns. Most codebases need roughly 10 of them 80% of the time. The patterns here were selected based on frequency of use in actual codebase audits: they address the most common structural problems that cause high cyclomatic complexity, low cohesion, and maintenance friction.

If you are working through a tech debt solution program, these are the techniques your engineers will reach for most often.


Extraction Patterns

1. Extract Method

The most commonly needed refactoring pattern. When a function is too long or does more than one thing, extract a section into a new, named method.

Before:

def process_order(order):
    # validate
    if order.total <= 0:
        raise ValueError("Invalid total")
    if not order.customer_id:
        raise ValueError("Missing customer")
    # calculate discount
    discount = 0
    if order.total > 100:
        discount = order.total * 0.1
    order.total = order.total - discount
    save(order)

After:

def process_order(order):
    validate_order(order)
    apply_discount(order)
    save(order)

def validate_order(order):
    if order.total <= 0:
        raise ValueError("Invalid total")
    if not order.customer_id:
        raise ValueError("Missing customer")

def apply_discount(order):
    if order.total > 100:
        order.total -= order.total * 0.1

Apply when a function exceeds 20-30 lines, when you need to add a comment to explain a block of code (the comment text becomes the method name), or when a section of code could be reused elsewhere.

2. Extract Variable

Replace a complex expression with a named variable to make intent clear.

Before: if user.age >= 18 and user.country in ALLOWED_COUNTRIES and not user.is_banned:

After: is_eligible = user.age >= 18 and user.country in ALLOWED_COUNTRIES and not user.is_banned

3. Extract Class

When a class has too many responsibilities, extract a subset of fields and methods into a new class. This is the most impactful pattern for reducing coupling. See the detailed guide on extract class refactoring for a full walkthrough.

4. Extract Interface / Abstract Class

When multiple classes share behavior, extract an interface that captures the shared contract. This enables dependency injection and makes the code testable without relying on concrete implementations.


Organization Patterns

5. Move Method

If a method uses data from another class more than from its own class, move it to the class it uses most. This improves cohesion and reduces coupling between classes.

6. Rename

The simplest and most impactful pattern for readability. Rename variables, methods, and classes to accurately reflect their purpose. A function named process() that actually validates payment data should be named validate_payment(). Modern IDEs make rename refactoring safe by updating all references automatically.

7. Introduce Parameter Object

When a function takes more than three or four parameters, group related parameters into an object.

Before: def create_user(first_name, last_name, email, phone, country):

After: def create_user(contact_info: ContactInfo):

This reduces function signatures, makes the code easier to read, and makes it easier to add validation logic to the parameter group.

8. Replace Magic Number with Named Constant

Numeric and string literals scattered through code obscure intent and make changes risky.

Before: if response.status_code == 429:

After: if response.status_code == HTTP_TOO_MANY_REQUESTS:


Simplification Patterns

9. Replace Conditional with Polymorphism

When a method has a switch or if-else chain that branches on a type or category, replace the conditional with a class hierarchy. Each subclass implements the behavior for its case.

Before:

def calculate_shipping(order):
    if order.type == "standard":
        return order.weight * 2.5
    elif order.type == "express":
        return order.weight * 5.0
    elif order.type == "overnight":
        return order.weight * 10.0

After: each shipping type is a class with a calculate() method. The conditional disappears; the right method is called via polymorphism.

This is the correct pattern when the list of types grows over time, as adding a new type requires only a new class rather than modifying an existing switch statement.

10. Decompose Conditional

When a complex boolean condition is repeated or difficult to understand, extract it into a named method whose name explains the condition.

Before: if start <= date <= end and not holiday and region in active_regions:

After: if is_active_service_period(date, region):


Conclusion

These 10 refactoring patterns address the majority of structural problems in real-world codebases. Extract method alone eliminates most oversized function problems. Rename improves readability across the entire codebase over time. Extract class and move method together address coupling and cohesion problems that slow development.

The key to applying these patterns safely is to use IDE automation where available, work one pattern at a time, and verify with tests after each change. Applied consistently, these techniques reduce cyclomatic complexity, improve test coverage, and make codebases that used to take weeks to navigate readable in days.

Does your codebase have these problems? Let’s talk about your system