skip to content

Which account rules can the type itself make unrepresentable, and which still need a check at one construction point?

level: seniorimportance: should knowfreq 40%

answer

  1. sort rules by what they depend on
  2. fields together, or something outside
  3. shape cannot grip a format
  4. one door, returning value or failure
  5. a rule that goes stale is not a property

basics

~20 s

Rules about which fields coexist can be encoded as variants; rules needing values the type does not hold — a format, a comparison against other records, anything time-dependent — cannot. Those move to one construction point that returns the strict value or a failure.

solid answer

~40 s

Encode the rules that are about *which fields go together*: an account is invited with no credential or active with one, so two variants remove the contradictory pairings outright. Rules whose inputs are not in the value resist that — whether an address is well-formed, whether it is unique across every other account, whether an invitation has expired as of now. Those depend on a format, on data elsewhere, or on the clock, and no arrangement of this record's fields forbids a violation. The working design puts them in one place: a single construction point that takes raw input and returns either the strict value or a named failure. After that point the value is trusted, because the only way to hold one is to have passed through it.

go deeper

for a junior

Recall the split: rules about which fields go together can be built into the shape, and rules about a field's contents or about other records cannot.

for a middle

Explain why the shape cannot enforce a contextual rule and describe the single construction point that returns either the strict value or a named failure instead of a flag.

for a senior

Show the judgment: sort real rules by their dependency, name the one door, argue why exclusivity is what makes downstream code able to skip re-checking, and price the translation at the edges.

for a principal

Decide where the line sits for a codebase other teams extend — which invariants are worth encoding, how many variants a model can carry before it is harder to read, and what you would write down as the house rule.

## Two kinds of rule Invariants divide cleanly by what they depend on, and the division decides which technique applies. - **Structural rules** relate the fields of the value to each other: this field is meaningful only when that one holds, these two are never both set, exactly one of these three is present. The account rule — a credential exists exactly when the account is active — is structural. - **Contentful or contextual rules** depend on something the value does not contain: the internal format of a field, a comparison against other records, the current time, a decision made elsewhere. Structural rules can be made **unrepresentable**. Split the record into one variant per legal situation, put in each variant exactly the fields that situation has, and the contradictory combinations lose their constructor. Contextual rules cannot be dissolved this way, because the shape has nothing to grip: no arrangement of an address field forbids a malformed address, and no arrangement of one account's fields can know what the other accounts contain. ## Where the second kind goes They go to **one** place: a construction point that is the only way to obtain the strict value. It takes the loose input — the stored row, the incoming message, the form — applies the rules it can apply, and returns either a value of the strict type or a named failure. Two properties make it work, and both must hold: 1. **It is the sole producer.** If the strict type can also be built directly, the construction point is advice rather than a guarantee, and the invariant survives only as long as everyone remembers. 2. **It returns a failure rather than a flag.** Handing back the value with an "unchecked" marker re-creates the flag-plus-field problem one level up, and every reader is back to asking. After that point, downstream code does not re-check, because holding the value *is* the evidence. That is the whole transfer: the rule is asserted once and then carried by the type rather than by discipline. ## Judging which rules go where | Rule | Depends on | Technique | |---|---|---| | credential present exactly when active | this value's own fields | variants — unrepresentable | | exactly one contact method, never two | this value's own fields | variants — unrepresentable | | address is well-formed | the field's contents | construction point, then a distinct strict type | | address is unique among accounts | other records | construction point at the boundary that can see them | | invitation not expired | the current time | checked when used, never a stored property | The last row is the one that catches experienced engineers. A rule whose truth changes without the value changing is not a property of the value at all. Storing "not expired" inside the account makes a claim that goes stale silently; the honest design keeps the invitation's time and answers the question at the moment it is asked. ## The cost side Encoding structure has a price, and pretending otherwise gets the technique rejected in review: - **More names.** Each variant needs a name the team agrees on, and readers must learn them. - **More conversion at the edges.** Storage and transport shapes stay loose, so something must translate in both directions, and that translation is real code to write and test. - **Combinatorial blowup if overused.** Splitting on flags that genuinely vary independently produces a variant per combination; group only the fields that vary *together*. - **Refactoring reaches every reader.** Changing a widely-held shape touches every site that takes it apart, which is a benefit when the compiler lists them and a cost when the change is ill-judged. The decision rule that survives contact with real code: encode the invariants that are **stable, structural and widely relied on**; check the rest once, at a boundary, and make that boundary the only door. ## What a strong answer sounds like A senior candidate does not claim the type can carry everything. They sort the rules by what each depends on, show the structural ones dissolving into variants, name the single construction point for the rest, and state the property that makes it trustworthy — that there is no other way to obtain the value. Then they mention the cost honestly: more names, translation at the edges, and a limit past which more variants make the model harder to read rather than safer.

  • Why must the construction point be the only way to obtain the strict value?
    Because the invariant is carried by the fact that every instance came through it. If the type can also be built directly, holding one proves nothing, and every reader is back to checking. The guarantee comes from exclusivity, not from the check itself being thorough.
  • A rule says an invitation is valid for seven days. Why is that a poor field on the account?
    Its truth changes while the value does not, so any stored answer is correct only at the instant it was computed and silently wrong afterwards. Keep the invitation time, which is a genuine property, and evaluate validity when the question is asked.
  • When has the variant split gone too far?
    When variants start multiplying over fields that vary independently, giving one case per combination and a match expression nobody can read. Split on the fields that change together and that the domain names; leave genuinely independent data as fields of a variant.

saying these in an interview costs you the question

  • Claims a sufficiently rich type can encode every business rule
  • Leaves a second way to build the strict value beside the construction point
  • Returns the value with an unchecked marker instead of a failure
  • Stores a time-dependent verdict as a field on the value
  • Splits variants over fields that vary independently