Boolean Flag Arguments

Boolean Flag Arguments


One thing I keep seeing in code reviews -- and something I used to do myself years ago -- is functions shaped like this:

def my_func(condition: bool):
    if condition:
        return do_a()
    else:
        return do_b()

With call sites like:

my_func(True)
my_func(False)

And I almost never think this is a good idea. It looks neat, but it hides the real story. In practice, I’ve found that code like this nearly always reads worse than just having two explicit functions:

def do_a():
    return ...

def do_b():
    return ...

Then the caller does:

if condition:
    do_a()
else:
    do_b()

Callers choose the behaviour explicitly, and you don’t end up passing a random True/False into something and hoping you remember what it means.


And the best part: if somewhere else in your code you want to do A unconditionally, you can just call do_a() without manufacturing a boolean or pretending you care about the alternative path.


The issue with Boolean Flags


1. They violate the Single-responsibility principle (SRP)


A boolean flag is almost always shorthand for:


“This function does two things; please tell me which one.”


And the moment a function does two things, it stops being honest about what it is.


Here’s a classic example:

def save_user(user, validate: bool):
    if validate:
        validate_user(user)
    persist(user)

Is this a validator? A persistence layer? Both? It depends on a parameter that’s invisible at the call-site unless you go digging.


Split it:

def save_user(user):
    persist(user)

def validate_and_save_user(user):
    validate_user(user)
    persist(user)

Clear responsibilities. Clear intent. No guessing what the boolean really does



2. Flags set a precedence and tend to accumulate

 

Flags tend to start simple and then grow branches:

def process_items(items, async_mode: bool):
    if async_mode:
        enqueue(items)
        log_metrics(items, async_mode=True)
    else:
        for item in items:
            process(item)
        log_metrics(items, async_mode=False)

Now this one function has two execution paths, two logging behaviours, and two failure modes. Testing and debugging it becomes a pain.


When you split it:

def process_items_sync(items):
    for item in items:
        process(item)
    log_metrics(items, async_mode=False)

def process_items_async(items):
    enqueue(items)
    log_metrics(items, async_mode=True)

The complexity doesn’t disappear — but it becomes visible. And visible complexity is easier to maintain and understand up front.


A better pattern: let the function name carry the intent


Back to the original example:

def my_func(condition: bool):
    if condition:
        return a()
    else:
        return b()

Versus:

def do_a():
    return a()

def do_b():
    return b()

Usage:

if condition:
    do_a()
else:
    do_b()

Advantages:

  • No hidden branching

  • You can reuse do_a() anywhere without dragging along a boolean

  • Behaviour is testable in isolation

  • Code reads like English instead of a config file



“But it’s more code?”


Sure. It’s more lines.

But code is not priced per line.


You’re buying:

  • read-time clarity

  • safer future edits

  • explicitness over magic

  • functions that do exactly what they say


Small, explicit functions are cheaper to maintain than one ambiguous function.


Martin Fowler said this ages ago


Martin Fowler’s article, “FlagArgument,” makes the same point: boolean flags are a design smell.


He suggests two main alternatives:

  • Replace Parameter with Explicit Methods (the pattern above)

  • Or make behaviour a property of a different object (strategy pattern, types, etc.)


Different contexts, same underlying principle: flags hide complexity and muddle intent.


When are boolean flags actually okay?


Occasionally:

  • Low-level, mathy, well-understood operations (inclusive=True on a range-like function)

  • Internal helpers used within a tight, private scope (like in the same file)


Even then, I still ask myself:

  • Could this be two functions?

  • Does the flag make the function dishonest about its real responsibility?

  • Will someone six months from now know what True means here?


If the answers feel uncomfortable, I rip out the flag.


Wrap-up


Boolean flag arguments seem harmless, but they usually signal that your function is doing too much and being too coy about it.


They:

  • Violate single responsibility

  • Make call sites harder to understand

  • Obscure the true complexity of what’s going on

  • Age badly


The fix is almost always the same:


Split the function. Name the behaviour. Let the caller decide.


Write code that’s boring, honest, and explicit — and your future self (and your reviewers) will thank you.

Comments