Avoid Boolean Types
- Posted: 2026-06-11
- 4 min
In source code or databases, prefer enumerations over boolean types.
Use boolean types only for expressions that compute temporary values fed
to conditional expressions like if, while, or
SQL’s WHERE.
Using enumerations
For class variables and function parameters, prefer explicit enumerations, even if you expect them to never have more than two values.
Instead of:
SetStyle(red, true);
A customer will see something like:
SetStyle(red, Layer::Foreground)
For database fields, you can always capture more metadata rather than
just the boolean. For example, if you’re modeling whether a message has
been processed, instead of a boolean processed column, you
can add an optional timestamp that changes from null when the message is
processed.
This applies to protocol buffers, XML or JSON formats. In JSON, instead of:
{ ... "enabled": true }
Do something like:
{ ... "status": "enabled" }
Separate functions
If you have a function that receives, possibly among other parameters, one boolean controlling some behavior, a common approach is to split the function into two.
In our example, you could replace SetStyle(Color, Layer)
with SetForegroundStyle(Color) and
SetBackgroundStyle(Color) (and, in general, implement both
with a common internal function). Of course, combinatorial explosion
makes this unfeasible for functions that receive many boolean
inputs.
Rationale
This has a few advantages.
The first advantage is increased type safety. With enumerations, you end up using very specific types for each particular type of predicate. If you accidentally swap two boolean values (corresponding to different semantic properties), the compiler chugs along. Not so with different enums —the type system catches your mistake.
Of course, we could do something stupid like
enum MyBoolean { True, False } to negate this advantage.
Who would be so stupid as to do this? Well, every time we use a boolean
type directly we are, roughly speaking, doing this.
The second advantage, related, is that we make call sites
more readable (as the SetStyle example shows).
A third advantage, albeit smaller, is that we future proof our code. We makes it easier to support more values in the future. Perhaps a new status appears —beyond “enabled” and “disabled”, maybe your model unexpectedly gains support for “unavailable”, “lightweight”, or “preview_only”.
You could partially gain some of these advantages thorugh ghost types
(e.g., MessageStatus = NewType('MessageStatus', bool)), but
if you’re going to do this, you might as well just use an enum. In other
words, enums are already the perfect ghost-type-like mechanism for these
situations.
Boolean for temporary expressions and predicate checks
Of course, boolean types are necessary: they are required by control
mechanisms (if, while, etc., and also
high-order functions like any, …).
Adding a function that “checks” if the object has the predicate (i.e., returns a boolean) is okay. If we’re evaluating some complex logical expression —likely with some binary boolean operators— in order to decide which branch to take, it might be okay to temporarily store the result in a boolean variable. Don’t create single-use enums for temporary values constrained to a small scope. Boolean is the right type for such values.
The fundamental principle is: use boolean types for the result of expressions that check whether something (e.g., an instance, a data row, the current function call) matches a logical predicate; don’t store such values in your data (or, at least, don’t store them using boolean types).
“Everything is ephemeral, both what remembers and what is remembered.”
Of course, everything is, ultimately, temporary. In this context, “temporary” refers to values unlikely to survive for long: typically the evaluation of a statement (in a programming language) or a function call (in which the expression that produces them occurs).
Related
Fail loudly: a plea to stop hiding bugs We often make the mistake of hiding logical errors in our software to make it seem more robust. This hampers software maintainability and correctness.
Concrete types yield better maintainability: Why the common advice of “express your functions in generic types” doesn’t always yield better maintainability.
Source code: Minimizing merge conflicts: Teams making fast progress on any shared codebase inevitably face (source code) merge conflicts. What can we do?