Fundamentals 11 min read

Why utils.py Turns Into a Code Dump and 3 Better Alternatives

A bloated utils.py quickly becomes a maintenance nightmare because it mixes unrelated logic, hides business rules, and creates tight coupling, but by reorganizing functions into domain‑specific modules, class methods, or tiny generic utility packages you can restore clarity, testability, and scalability.

IT Services Circle
IT Services Circle
IT Services Circle
Why utils.py Turns Into a Code Dump and 3 Better Alternatives

Why utils.py Becomes a Code Dump

In many Python projects a single utils.py grows to hundreds or thousands of lines, stuffing date formatters, string helpers, validation logic, and business‑specific calculations together. New developers cannot understand the intent, and the file becomes a "no‑one‑dares‑touch" zone.

Three Core Problems

Problem 1 – Lost Context

Calling utils.validate_email() gives no clue whether the validation is for registration, newsletter signup, or a contact form, so different scenarios end up sharing the same function.

Problem 2 – Hidden Tight Coupling

Functions like calculate_discount embed business rules (VIP 8 % off, new user 9 % off). When the promotion changes, you must hunt down every call and risk breaking code.

Problem 3 – Business Logic Dump

Because "anything goes into utils", domain‑specific logic (order processing, user stats) ends up mixed with generic helpers, giving the illusion of reuse while actually creating chaos.

Better Pattern 1 – Domain‑Specific Modules

Put behavior close to the domain it belongs to.

Before :

# utils.py (800+ lines)
from utils import format_date, validate_input
result1 = format_date(some_date)
result2 = validate_input(some_input)

After :

# billing/dates.py
from billing.dates import format_invoice_date
# auth/validators.py
from auth.validators import validate_login_credentials
invoice_date = format_invoice_date(order_date)
is_valid = validate_login_credentials(username, password)

Better Pattern 2 – Class Methods / Properties

When data needs behavior, attach it to the data class.

Before :

# utils.py
def is_order_refundable(order):
    if order.status != 'completed':
        return False
    if order.created_at < datetime.now() - timedelta(days=30):
        return False
    if order.has_refund_request:
        return False
    return True

if is_order_refundable(order):
    process_refund(order)

After :

# orders/models.py
class Order:
    def __init__(self, status, created_at, has_refund_request=False):
        self.status = status
        self.created_at = created_at
        self.has_refund_request = has_refund_request

    @property
    def is_refundable(self):
        if self.status != 'completed':
            return False
        if self.created_at < datetime.now() - timedelta(days=30):
            return False
        if self.has_refund_request:
            return False
        return True

if order.is_refundable:
    order.process_refund()

Advantages:

High cohesion – all order‑related logic lives in Order.

Easy discovery – look at the Order class to find related behavior.

Simple testing – test Order.is_refundable in isolation.

Maintainability – change the refund rule in one place.

Better Pattern 3 – Small, Focused Utility Packages

Use tiny, purpose‑built modules instead of a monolithic toolbox.

Recommended project layout:

project/
├── core/
│   ├── validators.py      # generic validation helpers
│   ├── formatters.py      # generic formatting helpers
│   └── exceptions.py
├── billing/
│   ├── calculator.py
│   └── formatters.py
├── users/
│   ├── validators.py
│   └── services.py
└── utils/
    ├── date_utils.py      # truly generic date helpers
    └── string_utils.py    # truly generic string helpers

Example of truly generic utilities that belong in utils:

# utils/date_utils.py
def days_between(date1, date2):
    """Return absolute number of days between two dates."""
    return abs((date2 - date1).days)

def format_duration(seconds):
    """Convert seconds to "Xh Ym" format."""
    hours = seconds // 3600
    minutes = (seconds % 3600) // 60
    return f"{hours}h {minutes}m"

# utils/string_utils.py
def truncate(text, length, suffix="..."):
    """Shorten text to *length* characters, adding *suffix* if truncated."""
    if len(text) <= length:
        return text
    return text[: length - len(suffix)] + suffix

When Is a Function a Real Utility?

If the function works unchanged in a completely different project, it is likely a genuine utility; otherwise it belongs to a domain‑specific module.

Simple Decision Flowchart

Start
  ↓
Is the function mainly operating on a specific object?
  ↓
Yes → Make it a method/property (Pattern 2)
No
  ↓
Does the function belong to a particular business domain?
  ↓
Yes → Move to the domain module (Pattern 1)
No
  ↓
Is the function truly generic, stateless, and unrelated to business logic?
  ↓
Yes → Keep in a small utility module (Pattern 3)
No → Re‑think whether the function should exist at all.

Final Thoughts

Utility functions are like instant noodles – quick and convenient, but over‑reliance leads to a brittle codebase. Good design places behavior next to the data it manipulates, uses clear module boundaries, and reserves the "utils" folder for truly generic helpers.

Remember the three principles:

Keep behavior close to its data.

Prefer clear module structure over a vague toolbox.

Only stateless, universally applicable code belongs in a utility module.

Start today by locating the bloated utils.py in your project and refactor it; you’ll be surprised how much readability and maintainability improve.

software designrefactoringCode Organizationutils
IT Services Circle
Written by

IT Services Circle

Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.