skip to content

An account record pairs a boolean active flag with a nullable credential field — what does replacing it with two variants prevent?

level: middleimportance: must knowfreq 62%

answer

  1. two facts that must agree
  2. the shape outruns the rule
  3. flag and field can disagree
  4. each variant carries what it needs
  5. one conversion site, not many readers

basics

~20 s

It prevents the two fields from disagreeing. Two variants — invited carrying no credential, active carrying one — give the contradictory pairings no constructor at all, so no reader has to check the credential's presence and no code path can create a half-active account.

solid answer

~40 s

The flag and the nullable field vary independently, so the record can hold combinations the domain never allows: active with no credential, or invited with one. The rule lives in comments and in every function that touches the record. Splitting the record into two named variants — one for invited, carrying only the address, one for active, carrying the credential as a required field — means the bad pairings have no way to be built. A reader that has matched the active variant holds the credential unconditionally, so the absence check on that field disappears rather than being repeated. The rule is now stated once, in the type, instead of re-asserted at every call site.

code

pseudocode · 10 lines
pseudocode
record Account:
    address
    active          // boolean
    credential      // may be absent

// all four of these construct successfully
a = Account(address, active = false, credential = absent)   // invited
b = Account(address, active = true,  credential = secret)   // active
c = Account(address, active = true,  credential = absent)   // illegal, still buildable
d = Account(address, active = false, credential = secret)   // illegal, still buildable

go deeper

for a junior

Recall the shape of the problem: a boolean plus a field that is only sometimes filled in can be set to combinations the business does not allow. Naming those combinations out loud is most of the answer.

for a middle

Explain why a record holding both fields lets them vary independently, walk the four states and mark the two illegal ones, and show that the active variant makes the credential a required field rather than an optional one.

for a senior

Demonstrate the migration judgment: which read sites lose their guard, where the single conversion point goes, and how you move a live codebase onto the new shape without a flag day.

for a principal

Frame it as a cost question. Removing a state from the space is cheaper than guarding it at n call sites only while n grows; say when you would set this as a house rule and when a product of independent fields is the honest model.

## The shape that cannot hold its own rule An account is in one of two situations. It has been **invited**: an address was entered, nothing has been proven, and no credential exists. Or it is **active**: the person came back, proved the address, and a credential now exists. The domain rule is a single sentence — *a credential exists exactly when the account is active* — and it relates two pieces of data to each other. The usual first encoding puts both pieces side by side in one record: a boolean `active`, and a `credential` field that may be absent. That record is a **product**: it holds every field at once, and each field varies independently of the others. Independence is exactly the defect here. The record can be constructed with `active = true` and no credential, or with `active = false` and a credential already present. Neither is a situation the business recognises, yet the type represents both perfectly well. ## Counting what the shape allows Count the credential only as *present or absent* (its own contents do not matter for this argument). The record then has 2 × 2 = **four representable states**, of which the domain allows **two**: | Flag | Credential | Domain meaning | |---|---|---| | `false` | absent | invited — legal | | `true` | present | active — legal | | `true` | absent | active with nothing to authenticate against — never legal | | `false` | present | a credential for an account nobody proved — never legal | Half the states the type can hold are states the business has no name for. Every one of them is a bug waiting for a reader that assumes the other half. ## What the split changes Replace the single record with a closed choice of two named variants: *Invited*, carrying the address and the invitation time; *Active*, carrying the address and a credential that is a **required** field of that variant. Three things follow: 1. **The illegal pairings have no constructor.** There is no expression that produces an *Active* without a credential, because the variant's constructor demands one. The two contradictory rows of the table above stop being states the program can reach — not states it checks for, states it cannot name. 2. **The reader stops asking.** Code that has matched the *Active* case holds the credential directly. The absence check on that field is gone, not relocated: there is nothing there to be absent. 3. **The rule is written once.** The sentence "a credential exists exactly when the account is active" moves out of prose and out of scattered guard clauses and into the declaration. A reader learns it by reading the type, and a new team member cannot violate it without the code failing to compile in a statically typed language, or without visibly constructing a variant that does not exist in a dynamically typed one. ## The check does not vanish — it moves The outside world still hands you a flag and an optional credential: a row from storage, a message from another service, a form. Something must still decide which variant that input becomes. The difference is *how many* places do it. In the flag-plus-nullable design, every function that reads the record re-derives the rule and each one may get it wrong differently. In the two-variant design, exactly one place — the point where external data is turned into the domain value — makes the decision, and everything downstream inherits it. That is the real payoff: one conversion site instead of n reading sites, where n grows with the codebase. ## Where the smell shows up in review The pattern is easy to spot once named. Look for a boolean whose truth changes which *other* fields are meaningful, and for comments of the form "only set when". A nullable field explained by a neighbouring flag is the same defect with different spelling. So is a field documented as "ignored unless". Each of these is a rule the shape is not carrying. ## When the split does not pay - **When the fields genuinely are independent.** If any combination is legal, a product is the right shape and splitting it invents distinctions the domain does not have. - **When there are many such flags.** Splitting k independent flags into variants produces a combinatorial explosion of cases; group the ones that vary together and leave the rest as fields. - **At a boundary that must accept anything.** Parsing and transport shapes deliberately admit malformed input so they can report it; the strict shape is what the boundary produces, not what it consumes. - **When the two variants share almost all behaviour.** If every reader immediately collapses both cases back into one, the split adds ceremony without removing a bug. ## What an interviewer is listening for A weak answer says "add a validation check". A strong answer says which states the current shape admits, which of them the domain never allows, and how the redesign removes them from the space rather than guarding against them — then names the one place the conversion now lives.

  • Where does the decision go once the record is split in two?
    To the single point where external data becomes a domain value — the conversion that reads the stored flag and optional credential and returns one variant or a failure. Downstream code inherits that decision instead of re-deriving it, so the rule is applied once rather than at every read site.
  • The team says a null check at each call site is equivalent. What do they lose?
    Nothing enforces that the checks agree or that a new call site adds one. Each check is an independent chance to handle the impossible case wrongly — silently defaulting, throwing, or treating the account as active. The variant split removes the case rather than multiplying the places that must remember it.
  • When is a boolean flag beside an optional field the right design after all?
    When the two are genuinely independent — when all four combinations are meanings the domain recognises. The smell is not the flag; it is a flag whose value decides whether another field is meaningful. If no combination is forbidden, there is no illegal state to remove.

A form with a tick-box and a blank line lets someone tick the box and leave the line empty; two different forms, one of which has no blank line at all, makes that mistake impossible rather than merely wrong.

saying these in an interview costs you the question

  • Says a null check at every call site is as safe as the split
  • Thinks the two variants duplicate the record for no gain
  • Claims a team convention is enough to keep the fields in step
  • Treats the nullable field's comment as the enforcement
  • Believes the split deletes the conversion rather than moving it