Function names should convey the intent, not the implementation

Every time you call a function or use a library, you’re using an abstraction. Writing in a high-level language instead of machine code is an abstraction. Using React is using an abstraction. Using Pytorch is using an abstraction. Abstractions are everywhere.

However, premature abstraction is the root of all evil in software engineering. You can’t introduce abstractions everywhere; no one likes to click through ten different levels of depth just to understand what actually is happening with the data and where it is happening.

How should you go about writing the building block of an abstraction - a function? You need to convey the intent of the code, not the implementation. This way, the user understands the “why” of the code rather than the “what”. However, it should be done at the highest level possible such that the “meat” of the implementation is as visible as possible.

Let’s look at a concrete example:

def restrict_user_if_reported_3_times_in_7_days(user):
    reports = Report.objects.filter(
        reported_user=user, 
        created_at__gte=datetime.now() - timedelta(days=7)
    )
    if reports.count() >= 3:
        user.status = 'restricted'
        user.save()

At first glance, it seems fine. It tells you exactly what it does. However, that is the issue. This function is named based on its implementation, not its purpose. What happens when the business decides that the threshold should be 5 reports in 10 days? Or when they want to factor in the severity of reports? Suddenly, your function is no longer useful.

The problem isn’t that the function doesn’t work. It’s that the name is fragile. A better name would be evaluate_user_for_restriction(). This conveys the intent, not the implementation:

REPORT_THRESHOLD = 3

def evaluate_user_for_restriction(user):
    recent_reports = Report.objects.filter(
        reported_user=user, 
        created_at__gte=datetime.now() - timedelta(days=7)
    )
    if should_restrict_user(recent_reports):
        user.status = 'restricted'
        user.save()


def should_restrict_user(reports):
    return reports.count() >= REPORT_THRESHOLD

This approach aligns with Domain-Driven Design. You’re using language that bridges the gap between your code and the business domain. Your code becomes more resilient to change and easier for both developers and domain experts to understand.

In our original function restrict_user_if_reported_3_times_in_7_days(), we’ve embedded implementation details in the name. However, the domain concept here is actually “new user,” not “7 days.” Our refactored code encapsulates business rules within well-defined functions. should_restrict_user() contains the specific criteria for restriction, making it easy to locate and modify as policies change. This encapsulation makes the code more flexible and extensible.

The main function, evaluate_user_for_restriction(), describes the process in business terms without the technical details. The code is self-documenting. It is easier for new developers to understand the business processes encoded in the software.

By structuring our code this way, we create a model that closely reflects the business domain. When someone from the team asks where the code is for “evaluating users for restriction,” developers can point to a function with that exact name. This shared understanding reduces miscommunication and makes the codebase more adaptable to changing business needs.

This approach improves testability, as each function can be verified independently. It also enhances the code’s resilience to change. If the business decides to alter the criteria for user restriction, we know exactly where to make that change (should_restrict_user()).


def get_recent_reports(user, days):
    return Report.objects.filter(
        reported_user=user, 
        created_at__gte=datetime.now() - timedelta(days=days)
    )

def restrict_user(user):
    user.status = 'restricted'
    user.save()

def evaluate_user_for_restriction(user):
    recent_reports = get_recent_reports(user, 7)
    if should_restrict_user(recent_reports):
        restrict_user(user)

def should_restrict_user(reports):
    return reports.count() >= REPORT_THRESHOLD

On first glance, the above example may look like it’s using good principles. The functions are semantically separated and “easier to read”. However, this is a clear example of an over-abstraction. Breaking down simple logic into numerous tiny functions can make things harder to read rather than enhance it.

For instance, restrict_user() is a two-line function that doesn’t necessarily warrant its own definition. Similarly, get_recent_reports() might be overkill for a query that’s only used once. This level of granularity can make it harder to parse what’s actually happening at a higher level, as developers need to jump between multiple function definitions to understand the flow. You need to find a good balance; the code needs to be abstract enough to be flexible and domain-oriented, but not so much that it obscures the core logic or makes the code unnecessarily complex to follow.


Clear function names are a form of documentation. They guide developers through the codebase, helping them understand the system’s architecture and business logic without diving into every function’s implementation.

Compare these two functions:

def check_user_activity_and_update_status():
    # Implementation...

def enforce_account_usage_policy():
    # Implementation...

The first function name is a red flag. It’s describing its operations and doing more than one thing. The second name clearly communicates its purpose without revealing implementation details.

Instead of thinking about what a function does, think about why it exists. What problem does it solve? What question does it answer? This approach not only improves naming but also provides insights into your code’s structure and the problems you’re solving.


Let’s play devil’s advocate. Are there cases where more specific, implementation-revealing names might be better? Sometimes, yes. In a small script or a highly specialized module, a name like block_ip_after_5_failed_logins() might be fine. In performance-critical sections, hinting at the implementation in the name, like get_user_permissions_from_cache(), could be beneficial.

Consider Python’s list.sort() and sorted(). One modifies the list in place, the other returns a new list. The names tell you exactly what to expect. If we followed the “name by purpose” rule strictly, we might have ended up with something like get_ordered_items() for both. That would be a mistake. Sometimes, the implementation is the purpose.

There’s also the challenge of balancing brevity and clarity. While descriptive names are generally good, a function named evaluate_user_behavior_and_apply_appropriate_restrictions_based_on_policy() might make code less readable due to its length.

And what about evolving business rules? If the user restriction policy varies by user type or region, a simple evaluate_user_for_restriction() function might hide too much complexity:

def evaluate_user_for_restriction(user):
    if user.account_type == 'premium':
        threshold = PREMIUM_REPORT_THRESHOLD
        period = PREMIUM_EVALUATION_PERIOD
    else:
        threshold = STANDARD_REPORT_THRESHOLD
        period = STANDARD_EVALUATION_PERIOD

    recent_reports = get_recent_reports(user, period)
    if recent_reports.count() >= threshold:
        restrict_user(user)

This function’s complexity isn’t captured by its name. There’s no easy answer here - it’s a trade-off between simplicity and accuracy that depends on your project’s specific context.

So when is it okay to name a function after its implementation? Here’s what I’ve noticed:

  1. When the implementation affects performance dramatically.
  2. When it changes the function’s behavior in ways users need to know about.
  3. When the implementation is the main feature.
  4. In very low-level code where precise control matters.
  5. When you’re trying to educate users about how things work under the hood.

In the end, the art of naming functions well is about clear communication. It’s about creating abstractions that encapsulate complexity and expose meaning. The trick is knowing which details matter to your users. Understand your audience.