refactoring

Refactoring is the process of changing the internal structure of your source code to make it easier to understand and cheaper to modify without changing its observable behavior.

In this context, you can apply the following refactoring best practices:

  • Refactor in small, safe steps. Make one focused and small change at a time. For example, rename a function, extract a helper method, or simplify a loop. This incremental approach allows you to improve your code gradually, reducing the risk of introducing bugs and regressions.
  • Let tests guide your refactors. Maintain a solid test suite and run it during refactoring to ensure consistent behavior across all code changes. When behavior is covered by tests, you can reorganize code with more confidence. Tests confirm that the code’s behavior hasn’t changed.
  • Look for common code smells. Search for smelly things, such as long functions, deeply nested conditionals or loops, repeated code fragments, and objects with many responsibilities that know too much. These are signs that refactoring is needed. Address these gradually instead of waiting for a big rewrite.

To see how refactoring can improve your code’s quality, check the example below:

🔴 Avoid this:

Python
def find_duplicate_emails(users):
    duplicates = []
    seen = []
    for user in users:
        email = user.get("email")

        if email is None:
            print("Missing email for", user.get("id"))
            continue

        # Check if we've seen this email before
        already_seen = False
        for index in range(len(seen)):
            if seen[index] == email:
                already_seen = True
                break

        if already_seen:
            duplicates.append(email)
        else:
            seen.append(email)

    print("Found", len(duplicates), "duplicate emails")
    return duplicates

This function works, but it has several issues. It combines multiple responsibilities, including validation, logging, and duplicate detection. It uses a nested loop to check seen emails, which results in quadratic time complexity as the input data grows. Finally, the flag, manual indices, and print() calls make the logic harder to follow and test.

Favor this:

Python
from collections import Counter

def _extract_emails(users):
    return [user["email"] for user in users if "email" in user]

def find_duplicate_emails(users):
    emails = _extract_emails(users)
    counts = Counter(emails)
    return [email for email, count in counts.items() if count > 1]

In this refactored version, you first split responsibilities. Now, the _extract_emails() helper focuses on email validation and extraction, while find_duplicate_emails() focuses on detecting duplicate emails.

You’ve also used the Counter class and leveraged comprehensions, which are more readable, efficient, and Pythonic. You also removed the print() calls, which makes the functions more reusable, flexible, and less coupled to your specific use case. Now, callers can decide how to report the results.

Tutorial

Refactoring Python Applications for Simplicity

In this step-by-step tutorial, you'll learn how to refactor your Python application to be simpler and more maintainable and have fewer bugs. You'll cover code metrics, refactoring tools, and common anti-patterns.

intermediate best-practices

For additional information on related topics, take a look at the following resources:


By Leodanis Pozo Ramos • Updated Feb. 3, 2026