A check on an account returns a bare boolean — what information has that return type thrown away?
answer
- one bit, no name
- the verdict travels, the evidence does not
- two booleans are interchangeable
- failure reasons collapse into one value
- return what the branch established
basics
~20 sEverything except one bit. The boolean says a check succeeded but not what was checked, what it produced, or why it failed — so the caller must re-derive all of it, and nothing stops the two bits from being confused at the call site.
solid answer
~40 sA boolean carries one bit and no name for what that bit means. When a check on an account returns `true`, the caller knows only that something passed: not which rule passed, not the credential the check already extracted, and on `false` not which of several failures occurred. The caller re-does work the check already did, and the value is interchangeable with every other boolean in scope, so passing the wrong one type-checks perfectly. That loss of meaning is called **boolean blindness**. The fix is to return the evidence instead of the verdict — a variant carrying what the successful branch established, and a variant naming the failure — so the caller reads the outcome rather than reconstructing it.
code
pseudocode · 14 lines// blind: the verdict survives, the evidence does not
function isActive(account) -> boolean
if isActive(account):
credential = account.credential // may still be absent as far as the type knows
// carrying the evidence instead
function activation(account) ->
Activated(credential)
| NotActivated(reason)
match activation(account):
case Activated(credential): use(credential)
case NotActivated(reason): report(reason)go deeper
Recall the core loss: a boolean tells you yes or no and nothing about what was asked or what was found, so the caller has to work it out again.
Separate the three losses — subject, work, reason — and show the call site re-deriving something the check already had. Then propose a return type that carries the evidence instead of the verdict.
Show where it bites in a real codebase: booleans stored in records and passed across boundaries, several in one signature, failure reasons that never reach a log. Say which of them you would change and which you would leave.
Set the boundary as a standard other teams can apply: which return types must carry evidence, which may stay boolean, and how you keep the rule from turning into ceremony on every predicate.
## One bit, no name A function checks an account and returns `true` or `false`. The verdict travels; everything that produced it stays behind. That is the whole of **boolean blindness**: the type retains the *answer* and discards the *question*, so the caller holds a value that means "yes" without any record of what it said yes to. Three specific losses follow, and it is worth separating them because candidates usually name only the first. - **The subject is gone.** A `true` returned from an account check is the same value as a `true` returned from a feature check, a parse or a permission test. Two booleans in the same scope can be swapped in an argument list with no complaint from the type. - **The work is gone.** The check very often *found* something on its way to the verdict — the credential it located, the parsed address, the matching record. Returning a boolean throws that away and the caller fetches it again, usually with an absence check that the check itself already performed. - **The reason is gone.** A `false` collapses every distinct failure into one indistinguishable value. Was the account merely invited, was the credential wrong, or was there no such account? The caller cannot tell and so cannot report anything useful. ## Why it belongs to this subject Boolean blindness is the same defect as a flag beside a nullable field, seen at the level of a return value instead of a record. In both cases a bare bit carries meaning that nothing names, and the correlated data sits somewhere the type does not connect it to. The classic shape is the pair: ``` if isActive(account): use(account.credential) // absence check demanded here, again ``` The guard proved something about the account, and then the type immediately forgot it. The next line has to ask a question whose answer was already known one line earlier, and nothing enforces that the two lines stay in agreement when someone edits one of them. ## Returning the evidence instead of the verdict The repair is to make the return type carry what the branch established: | Returned | What the caller must still do | |---|---| | `true` / `false` | re-find the credential, re-derive the reason, trust the guard | | a variant carrying the credential on success, a named failure otherwise | take the outcome apart and use what is inside it | In the account frame that is a choice of two variants: one carrying the credential the check located, one naming what went wrong — not yet active, no such account, credential mismatch. The caller takes it apart by shape; the success branch already holds the credential, and the failure branch already holds a reason it can report. Nothing is re-derived and nothing is assumed. ## When a boolean is exactly right This is not a rule against the type. A boolean is the honest return when: 1. **The question has genuinely two answers and no payload** — is this collection empty, is this number even. There is nothing the caller would want back. 2. **The name at the call site carries the meaning** — a well-named predicate used immediately in the condition it was written for, where the value never travels. 3. **The value has a very short life.** Blindness hurts in proportion to distance: a boolean created and consumed on the next line is hard to confuse; one stored in a record, passed through three layers and read elsewhere is the problem case. The smell is a boolean that **travels** — stored as a field, passed as a parameter, returned across a module boundary — and especially several of them side by side in one signature, where the call site becomes a row of unlabelled truths whose order is the only thing distinguishing them. ## What the interviewer is checking The name is not the point; the diagnosis is. A strong answer says which information the boolean dropped, shows the call site re-deriving it, and proposes a return type that carries the evidence. It also draws the line honestly — an emptiness test does not need a variant — because a candidate who wants to abolish booleans has learned a slogan rather than a trade-off.
- Two boolean parameters sit side by side in one signature. What specific hazard does that add?They are the same type, so swapping them at the call site is invisible to every check the type system performs and to most readers. The compiler cannot distinguish them, and the mistake surfaces only as wrong behaviour. Separate named variants, or distinct single-purpose parameters, remove the possibility.
- Does returning a named enumeration of outcomes solve the whole problem?It solves the reason and the naming: failures stop collapsing into one value and the outcome is no longer interchangeable with unrelated booleans. It does not solve the lost work, because a plain enumeration carries no payload. Variants that carry the credential on success recover that part.
- Where is a bare boolean return still the right design?Where the question truly has two answers and no payload, and where the value is consumed immediately at the call site rather than stored or passed on. Emptiness and parity tests are the honest cases. The smell is a boolean that travels far from the check that produced it.
A pass stamp on an envelope tells you an inspector approved it, but not what was inspected or what they found inside; you end up opening the envelope again anyway.
saying these in an interview costs you the question
- Says the caller can always look up why the check failed afterwards
- Thinks boolean blindness means booleans are a bad type
- Believes a naming convention prevents two booleans being swapped
- Treats the lost failure reason as the only loss
- Assumes the guard's knowledge is carried into the next line