A reusable function is one that you write once and call from multiple places, across multiple projects, without modification. It solves a general problem rather than a specific instance of that problem, and it makes no assumptions about the context in which it will be called beyond what its parameters declare. Writing reusable functions is a skill that develops with practice, and the principles that make a function reusable are the same principles that make any function easier to test, debug, and understand.
The articles in this Functions section have covered the mechanics: how to define functions, pass arguments, return values, and manage scope. This article focuses on the design principles that turn mechanically correct functions into genuinely reusable ones. If you have been following the section in order, you have encountered many of these principles already in specific contexts. The article on pure functions and side effects in Python explained why purity matters for reliability. The article on common Python function mistakes showed what happens when these principles are violated. Now we bring the principles together into a coherent approach to function design.
The single responsibility principle
A reusable function does one thing and does it well. Its name describes that one thing completely, and its implementation contains no logic that is not directly related to that purpose. A function called validate_email should validate an email address. It should not also normalize the address, store it in a database, and send a welcome message. Each of those is a separate responsibility that belongs in a separate function.
Single-purpose functions are easier to reuse because they are more likely to match a future need exactly. A function that both validates and normalizes an email address is only reusable in contexts that need both operations. A function that only validates can be used anywhere validation is needed, and a separate normalization function can be composed with it when both are required. Composing small functions into larger operations gives you more combinations from fewer building blocks.
The single responsibility principle also makes functions easier to name. A function that does five things needs a name that encompasses all five, and that name will be either vague or misleading. A function that does one thing can be named with a precise verb phrase that tells the caller exactly what to expect. The article on define and call Python functions covered naming conventions in detail, and single responsibility is what makes those conventions possible to follow.
Explicit dependencies through parameters
A reusable function declares everything it needs in its parameter list. It does not reach out to global variables, read from environment variables directly, or depend on the current working directory. All inputs come through parameters, which makes the function's dependencies visible in its signature and controllable by the caller:
# Not reusable: depends on a global variable
TAX_RATE = 0.08
def calculate_total(price):
return price * (1 + TAX_RATE)
# Reusable: tax rate is an explicit parameter
def calculate_total(price, tax_rate=0.08):
return price * (1 + tax_rate)The second version is more reusable because the caller controls the tax rate. It can be called with the default, with a different rate, or with a rate loaded from configuration. The function does not need to know where the rate comes from, and the caller does not need to modify global state to change the rate for a single call. The default value of 0.08 provides the convenience of the global variable without the coupling.
This principle extends to all external dependencies. A function that reads from a file should accept a file path or a file object as a parameter, not hardcode a path. A function that sends emails should accept the email configuration as a parameter, not read it from a global settings object. Parameters are the function's contract with its callers. Making every dependency a parameter makes that contract explicit and complete.
Returning results, not producing side effects
A reusable function communicates its result through its return value, not through side effects like printing, modifying global state, or mutating its arguments. A function that prints its result is only useful in contexts where printing is the desired output. A function that returns its result can have that result printed, logged, stored, forwarded, or used in further computation, depending on what the caller needs:
# Less reusable: prints the result
def format_name(first, last):
print(f"{last}, {first}")
# More reusable: returns the formatted string
def format_name(first, last):
return f"{last}, {first}"The returning version can be composed with other functions. Its output can be passed to print, written to a file, stored in a database, or concatenated with other strings. The printing version is a dead end; its output is gone as soon as it appears on the screen. When side effects are genuinely necessary, as they are for functions that write files or send network requests, isolate them from the computation so the core logic can be tested and reused independently of the side effect.
Keeping functions small and focused
A reusable function should be short enough to understand in one screenful of code, typically under twenty or thirty lines. This is not an arbitrary limit but a practical consequence of single responsibility. If a function genuinely does one thing, it rarely takes more than thirty lines to express that thing clearly. Longer functions usually indicate that multiple responsibilities have been merged, and the function should be split.
Splitting a long function into smaller ones forces you to name the intermediate steps, which clarifies what each step does and how they fit together. The original function becomes a coordinator that calls the extracted functions in order, and each extracted function is independently testable and potentially reusable in other contexts:
def process_order(order):
validate_items(order.items)
total = calculate_total(order.items, order.tax_rate)
charge_customer(order.customer_id, total)
send_confirmation(order.customer_email, order.id, total)Each function called by process_order has a single responsibility that is clear from its name. Process_order itself reads like a summary of the workflow. If a different part of the system needs to calculate totals without processing orders, calculate_total is available independently. This decomposition is the practical outcome of the design principles in this article applied consistently.
Rune AI
Key Insights
- A reusable function has a single clear responsibility, expressed in its name and its signature.
- Depend on parameters, not global state; return results, do not produce hidden side effects.
- Keep the parameter count low; group related parameters into objects when the count grows.
- Test functions in isolation to verify they are truly self-contained and do not depend on hidden context.
- The best reusable functions feel like built-ins: they are predictable, well-named, and work the same way every time.
Frequently Asked Questions
What makes a Python function reusable?
How many parameters should a reusable Python function have?
Should I always write pure functions for reusability?
Conclusion
Reusable functions are not an accident of clever coding. They are the result of deliberate design choices: keeping functions small, giving them clear responsibilities, making their dependencies explicit through parameters, and returning results rather than relying on side effects. These practices compound over time, turning a collection of one-off scripts into a personal library of reliable, composable functions that you reach for across projects.
More in this topic
Define and Call Python Functions
Learn the exact syntax for defining and calling Python functions with the def keyword, including naming rules, the function body, and how the call stack works.
Python Function Parameters and Arguments
Learn the difference between parameters and arguments, how to pass values by position and by keyword, and the rules for default parameter values.
Python Functions Explained
Learn what Python functions are, why they matter for organizing code, and how they form the foundation of non-trivial programs.