Architected by Chet, written by Antigravity
Let's get straight to the point (again): a boolean should never be your first move in a physical data model.
Last week, in The Boolean is Lying to You, we talked about how a boolean could ruin an OLAP dimensional model. In the warehouse, the problem with a boolean is that it duplicates a truth that already exists elsewhere (temporal boundaries).
But the temptation of the boolean doesn't vanish when you switch to an OLTP system. In a transactional database, the problem is the exact opposite: it destroys a richer truth and replaces it with a poorer one.
If anything, the drive for immediate application convenience has made it worse. You need to know if a user account is active, if a record is deleted, or if an order is shipped. The developer reflex is to slap an is_active, is_deleted, or is_shipped flag on the table.
Just like in the warehouse, the boolean is lying to you. In an operational system, lost fidelity means lost business context.
The is_deleted Tragedy: Timestamp Trumps Boolean
The most common offender is the soft delete: is_deleted = true.
It seems harmless. The application filters out WHERE is_deleted = false, and your data is "safe". But in an operational system, knowing that something was deleted is rarely enough. Within weeks, the business will ask: When was it deleted? Who deleted it? How long was it active before it was removed?
Your boolean is mute. It destroyed the temporal context of the event.
Instead of is_deleted, your first instinct should be a deleted_at timestamp. If deleted_at IS NULL, the record is active. If it's populated, you know exactly when the state changed. The application's WHERE clause is just as simple, but the database retains the full fidelity of the event.
The Boolean Pile-Up: Where State Machines Go to Die
Business processes are rarely binary. They are lifecycles. They are state machines. But the path of least resistance often leads to modeling these state machines as a pile of mutually exclusive booleans.
It starts innocently with is_draft = true.
Then the business process evolves, so we add is_published.
Then we need to pull it down temporarily, so we add is_archived.
Now you have a record where is_draft = true AND is_published = true. What does that mean? It means your application allowed an invalid state because your physical model didn't enforce mutual exclusivity. You forced the application code to manage the integrity of the state machine, and eventually, the code will fail.
If a record moves through a lifecycle, use a status_code (backed by a reference table) or an event-sourced ledger. A single status column makes mutually exclusive states explicit and enforceable. Booleans just allow for combinatorial explosions of invalid states.
The Tri-State Lie
A boolean promises two states: True or False.
But in a SQL database, a nullable boolean actually has three states: True, False, and NULL.
What does a NULL boolean mean in your application? Does it mean "False"? Does it mean "Unknown"? Does it mean "Not Applicable"? When is_verified is NULL, did the verification fail, or has it just not happened yet?
When you use a boolean to represent business state, you inevitably back yourself into relying on this ambiguous third state. If you have three states, you don't have a boolean. You have a poorly labeled lookup table.
The Indexing Bonus: The Nerd Special
There's a physical performance argument here, regardless of which database engine you use.
Let's say you have a transaction processing table, and you use is_processed = false to find work that needs to be done. If you index that boolean, you're indexing the entire table.
Instead, if you use a processed_at timestamp, you get a massive performance feature for free by using a partial (or sparse) index.
By creating an index specifically for the rows WHERE processed_at IS NULL, your index only contains the tiny fraction of records that actually need processing. It stays perfectly sparse, incredibly small, and lightning fast. A generic boolean flag robs you of this elegant optimization.
Downstream Devastation: Why the Warehouse Cares
It’s tempting to think that an OLTP shortcut only affects the application layer. But the damage flows downstream. When you overwrite a state with a simple boolean flag, you aren't just making a lazy choice for the transactional app—you are permanently destroying data that your analytical systems desperately need.
- Transitions are destroyed. If you just update
is_converted = trueoris_canceled = true, you only know the final outcome. You lose the sequence of events. You can no longer calculate the duration between states, identify bottlenecks, track SLAs, or analyze churn. Operational analysis, process mining, and machine learning all require transitions to figure out why something happened. A boolean destroys the transition and leaves you with a tombstone. - Time-Travel and CDC (Change Data Capture). When your OLTP system relies on event logs, timestamps, or explicit status histories, extracting that data into your OLAP environment is deterministic and robust. You can perfectly reconstruct what the business looked like at any given second. If you rely on flipping a boolean in place, you force the warehouse to frantically poll and capture those fleeting changes before they are overwritten again, inevitably missing rapid transitions.
Stop Hiding the Business Process
In OLTP, the database is the engine of the business. When you reduce a business event (like a cancellation, a deletion, or a publication) to a boolean flag, you are erasing the context of that event. You are optimizing for a temporary application shortcut instead of modeling the reality of the domain.
Just like in dimensional modeling, the rule stands: a boolean requires a waiver. Don't reach for it just because it's easy. Force yourself to ask: "Does this state have a history? Does it have a timeline? Is it part of a larger lifecycle?"
Almost every time, the answer is yes. And almost every time, the boolean is the wrong move.

