Skip to content
TkDodo's blog

No love for boolean parameters

Feb 28, 2021 — JavaScript, TypeScript, ReactJs
No love for boolean parameters
Photo by Alexander Sinn

Ah, booleans. 0 or 1, true or false. Always either one of them, never something in between. So simple and predictable. At the end of the day, all code we write winds up in lots of zeros and ones.

There is nothing wrong with booleans per se. We use them for conditions every day:

boolean condition
// ✅ boolean condition
if (user.age() < legalAge) {
return 'Sorry, you are too young to use this service'
}

But using them for parameters to a function can be an indication of bad design for various reasons:

Single responsibility principle

A function should do one thing, and one thing only. Passing a “flag” to it often indicates that a function does two things at once, thus violating this principle (opens in a new window). For example:

single responsibility
// 🚨 booleans as a function parameter
function createReport(user: User, sendEmail: boolean) {
// create the report here
const reportData = ...
if (sendEmail) {
sendReport(user.email, reportData)
}
return reportData
}

There seem to be some cases where we want to send the report as email directly after creating it, and some cases where we don’t. But why entangle this in the createReport function? That function should only create the report, and nothing else. The caller can decide what they want to do with it.

They are confusing

Flags can be confusing, especially in languages where you don’t have named parameters. Take for example this signature of equals (opens in a new window) from the Kotlin standard library:

equals with ignore case flag
fun String?.equals(other: String?, ignoreCase: Boolean): Boolean
// Returns true if this string is equal to other,
// optionally ignoring character case.

As opposed to the first example, the function doesn’t do two things at once, it does one thing in two different variations - an important difference. This can be highly confusing when you have to read the call-side that looks something like this:

???
"foo".equals("bar", true)
"foo".equals("bar", false)

How should we know what true means in this context. Even worse, what would false mean? Does it maybe negate the equals comparison? Scala has solved this differently with two methods: equals (opens in a new window) and equalsIgnoreCase (opens in a new window). Each does one only one thing - no guesswork here.

More guesswork

Before you look it up here (opens in a new window) - what do you think this boolean flag on Groovy’s List.sort method means:

sort
["hello","hi","hey"].sort(false) { it.length() }

In case it isn’t obvious to everyone:

Totally logical and intuitive api, not confusing at all 🤷‍♂️

Impossible states

Booleans make it easy to create impossible states. Suppose you have a metric of some sorts, and you want to format that. It might be a “normal” number, but it might also be a percentage value. So you decide to model the formatting function like this:

format metric
function formatMetric(value: number, isPercent: boolean): string {
if (isPercent) {
return `${value * 100}%`
}
return String(metric)
}

This is rather rudimentary number formatting function, but apart from that, it doesn’t look too bad. Frankly, the first “flag” you add to a function usually looks very innocent.

The second flag

Requirements change over time (as they tend to do), and now we have to support currencies for some of our metrics as well. Starting from the above formatting function, we are tempted to add another flag, isCurrency

🚨 currency formatting
function formatMetric(
value: number,
isPercent: boolean,
isCurrency: boolean,
): string {
if (isPercent) {
return `${value * 100}%`
}
if (isCurrency) {
return // imagine some currency formatting is returned here
}
return String(metric)
}

Our code works, we write tests, add the currency flag if we have a currency metric, and all is well.

Except it isn’t.

Adding one boolean doesn’t add one more state - the amount of states grow exponentially. Two booleans means four states, three booleans means eight possible states etc. What happens if we call our above function with:

formatMetric(100, true, true)

The answer is: you can’t know. It’s an implementation detail which flag is checked first. It’s also an impossible state: A metric cannot be percent and currency at the same time. Such impossible states are frequently introduced with boolean parameters. I recently encountered a function with 8 booleans as input - turns out, it only had 3 actual states, the rest were variations thereof.

Resist the urge

To avoid impossible states, resist the urge of adding the first boolean parameter. It is infinitely easier for humans to extend existing patterns instead of recognizing anti-patterns and refactoring them. If there is one boolean, there will be a second. If we start of with an enumeration of possible states, it is much more likely that this will be extended instead:

✅ metric variant
function formatMetric(value: number, variant?: 'percent'): string {
if (variant === 'percent') {
return `${value * 100}%`
}
return String(metric)
}

Now we can extend the variant to 'percent' | 'currency', and only have three states to work with instead of four. Of course, you can also explicitly include the default (standard) variant instead of using undefined.

Moar advantages

Further advantages of a single variant property include:

exhaustive variants
type MetricVariant = 'standard' | 'percent' | 'currency'
function formatMetric(
value: number,
variant: MetricVariant = 'standard',
): string {
switch (variant) {
case 'percent':
return `${value * 100}%`
case 'currency':
return // imagine some currency formatting is returned here
case 'standard':
return String(metric)
}
}

We also do the same when creating React components, or have you seen a Button with an isPrimary and isSecondary flag? Of course not - because how can they be both at the same time?

button variants
// 🚨 Don't do this
<Button isPrimary isSecondary />
// ✅ Do this
<Button variant="primary" />

The wrong abstraction

Oftentimes, flags are added because we see similarities to existing code, and we don’t want to repeat ourselves, keeping everything DRY (opens in a new window).

There is lots of good literature available on that topic, showing why we shouldn’t do this and what we could do instead:

I can recommend them all, and for starters, resist the urge of adding the next boolean parameter to your codebase.


No matter if you like booleans, or not, or both at the same time, leave a comment below. ⬇️

Like the monospace font in the code blocks?

Check out monolisa.dev

Bytes - the JavaScript Newsletter that doesn't suck