April 22, 2026 QSEN#43
QSEN #43 – Inconsistent function interface
Hello!
In many scientific projects we’ve seen an anti-pattern of having combinations of settings which don’t make sense together.
Example
Suppose we have a function that runs a simulation of an ideal gas:
def run_simulation(initial_v: float, initial_p: float, initial_T: float):
...
So far, so good.
Now imagine a common use case is running simulations at constant volume. A natural extension might be:
def run_simulation(
initial_v: float, initial_p: float, initial_T: float, constant_v: bool = False
):
...
Later, we add constant pressure as well:
def run_simulation(
initial_v: float,
initial_p: float,
initial_T: float,
constant_v: bool = False,
constant_p: bool = False,
):
...
At first glance this looks fine. But we start running simulations over multiple parameters/settings, we might end up with code like this:
results = []
for constant_v in [True, False]:
for constant_p in [True, False]:
results.append(
run_simulation(1, 2, 3, constant_v=constant_v, constant_p=constant_p)
)
We just created a configuration where both volume and pressure are constant.
These constraints either cannot be satisfied simultaneously, or lead to useless results that will just waste computational resources. The core problem is that the API suggests these options are independent, while in reality they aren’t.
So the core issue we face is that the interface allows combinations of inputs that do not correspond to valid states of the system.
What can we do about it?
1. Do nothing
You can leave the interface as is and hope users won’t pass contradictory combinations. In practice, they will — especially when the API surface grows and the configuration is constructed in multiple places.
2. Validate at runtime
You can detect invalid combinations and raise an error:
if constant_v and constant_p:
raise ValueError("Cannot keep both volume and pressure constant.")
This is better than nothing, but it scales poorly. As you add more options (say constant_T), the number of invalid combinations grows quickly, and your validation logic becomes easy to miss, duplicate, or accidentally bypass.
Plus, you still allow the user to pass an invalid combination of inputs, which results in immediate failure.
3. Add separate functions
You can refactor your API to expose separate functions such as run_simulation_constant_volume and run_simulation_constant_pressure, and make the original run_simulation private.
In most cases the next option is better (see below), but this approach can be worth considering when you cannot change the original interface, or when you want to expose a very small API surface.
4. Model the concept explicitly
If the user is making a single choice (“what is held constant?”), represent it as a single parameter:
def run_simulation(
initial_v: float,
initial_p: float,
initial_T: float,
constant: str | None = None,
):
...
This reduces the number of arguments, makes the intent clearer, and is much easier to extend. Most importantly, it makes invalid combinations impossible to express.
Extra pitfall
Before you go fixing the API in your code, let’s take a look at how we can break the last suggestion. Let’s say the implementation is:
def run_simulation(
initial_v: float,
initial_p: float,
initial_T: float,
constant: str | None = None,
):
if constant == "pressure":
...
elif constant == "volume":
...
elif constant == "temperature":
...
else:
...
Now if we do:
run_simulation(1, 2, 3, constant="temperatre")
our code will run, but it will most likely not do what we wanted due to a typo.
That’s why rather than using free-form strings, it’s usually safer to restrict possible values, for example with Enum or Literal.
For instance, with Literal a type checker can catch typos:
from typing import Literal
Constant = Literal["pressure", "volume", "temperature"]
def run_simulation(
initial_v: float,
initial_p: float,
initial_T: float,
constant: Constant | None = None,
):
...
run_simulation(1, 2, 3, constant="temperatre") # type checker error
Conclusion
This issue usually shows up when an interface evolves gradually and we keep adding options ad hoc. If some settings are mutually exclusive, model the choice explicitly, rather than letting invalid states slip into your public API.
Have a nice day!
Michał & Konrad