Why does modelling payment as three variants beat one record with six optional fields for a reader?
answer
- count what each shape permits
- independent fields against grouped shapes
- sixty-four combinations, three meanings
- kind inferred versus kind labelled
- flat record is transport, choice is model
basics
~20 sSix optional fields admit sixty-four present-or-absent combinations, of which three are meaningful; three variants admit exactly three. The closed choice states which fields travel together, instead of leaving every reader to reconstruct that rule from prose or from other code.
solid answer
~40 sThe flat record is a product, so its components vary independently: six fields that may each be absent give `2^6 = 64` presence combinations, and the domain has three. The other sixty-one are shapes the type permits and the business does not, so every reader has to learn which is which from a comment, a validator or a neighbouring function. Three variants are a sum: the type itself says there are exactly three shapes and which fields belong to each, so the grouping is legible at the declaration rather than rediscovered at each call site. The flat record also cannot answer "which kind of payment is this?" without inspecting fields and inferring, while the choice answers it by its label.
code
pseudocode · 16 lines// shape A: one record, six optional fields
type PaymentFlat is a record of:
cardNumber: text or absent
cardExpiry: date or absent
accountRef: text or absent
routingCode: text or absent
voucherId: text or absent
voucherBalance: money or absent
// 2^6 = 64 present/absent combinations; 3 of them are meaningful
// shape B: the same domain as a closed choice
type PaymentChoice is one of:
Card(number: text, expiry: date)
BankTransfer(accountRef: text, routingCode: text)
StoreCredit(voucherId: text, balance: money)
// 3 shapes, each carrying exactly the fields that kind needsgo deeper
Notice the smell first: a record whose fields are only meaningful in certain groups, with the grouping explained in a comment. Being able to say "these fields travel together, so they should be one shape" is enough at this stage.
Put the number on it. Six optional fields admit sixty-four presence combinations against three meaningful ones, and the kind of payment becomes something each call site infers rather than something the value states.
Show you have lived with the drift: copies of the inference rule that disagree about the awkward case, and a migration path from the flat record that keeps both shapes alive at a boundary while the interior moves to the choice.
Own the rule and its exception: closed choices in the model, flat records at transport and storage, translation in one place. Say out loud what the closed set costs, so a team adding a fourth payment kind is not surprised by it.
This is the modelling argument the sum-and-product vocabulary exists to make, and it is worth being able to state numerically rather than as a preference. ## The record of everything The flat shape starts innocently. Payments began as card only, so the record carried a card number and an expiry. Bank transfer arrived, and rather than restructure, two more fields were added and all four were made optional. Store credit arrived and added two more. The result is one record with six components that may each be present or absent, plus — usually — a comment or a wiki page explaining which groups go together. What the type now says is: *any* of these fields may be present or absent, independently. What the domain says is: exactly one of three groups is present, complete, and the others are absent. Those two statements are very far apart, and the distance is measurable. ## Counting what each shape admits | Shape | Construction | Presence combinations admitted | Meaningful ones | |---|---|---|---| | Six optional fields | product | 2 x 2 x 2 x 2 x 2 x 2 = **64** | 3 | | Three variants, each with its own fields | sum of products | **3** | 3 | Sixty-four includes the all-absent record, records with a card number but no expiry, records with a card number *and* a voucher identifier, and every other mixture. Each of those is a value the type permits, so each is a case some reader, somewhere, will eventually wonder about. Counting only presence understates it, too: with the field values counted, the flat record's inhabitant count is larger still, while the closed choice's stays at three shapes. ## What the reader has to do in each case - With the **flat record**, a reader who wants a payment's kind must inspect fields and infer — "if the voucher identifier is present it is store credit" — and that inference is written out again in every function that needs it. - With the **closed choice**, the kind is the label the value already carries, and the fields that come with it are the variant's own payload. - With the flat record, a reader deciding whether a field is safe to use must know the rule. With the choice, the fields available are the ones the variant declares. - With the flat record, the grouping lives in prose that drifts from the code. With the choice, the grouping *is* the code, so it moves when the type moves. ## What the flat shape costs as it grows 1. **The rule is duplicated.** Each place that infers the payment kind from field presence carries a copy of the rule, and copies disagree over time — usually about the awkward case, such as a card number present with no expiry. 2. **Each new field doubles the permitted combinations.** A seventh optional field takes sixty-four to one hundred and twenty-eight, while the number of meaningful shapes rises by at most one. The gap between what the type permits and what the domain means widens with every addition. 3. **Nothing records the intended groups.** A newcomer reading the declaration learns the field names and nothing about which travel together, so the knowledge lives with whoever has been on the team longest. ## When the flat record is still the right call This is not an argument that optional fields are a defect. The flat shape is right when it genuinely reflects the domain — fields that really do vary independently, such as a set of optional contact details — and it is often forced at an interface or a storage format that offers only records of optional columns. The honest position is that the flat record is a **transport or storage shape**, and the closed choice is the **model**, and that the translation between them belongs in one place rather than being spread across the code that uses either. It is also worth being clear about what the closed choice gives up: the set of alternatives is fixed at the declaration, so a fourth payment kind is a change to the type and to the code around it. That is the trade being made — a fixed set of shapes in exchange for the type saying exactly what those shapes are. ## What an interviewer is listening for The strong answer is numeric and specific: *sixty-four permitted combinations against three meaningful ones*, plus the observation that the kind becomes a thing you infer rather than a thing you are told. The weak answer is aesthetic — "variants are cleaner" — which does not survive the follow-up about why. The other thing they listen for is whether you know when not to reach for the choice, because a candidate who converts every optional field into a variant has swapped one reflex for another.
- Where does sixty-four come from, and does it change once the fields carry values rather than presence alone?Sixty-four is presence alone: six independent fields, two states each, `2^6`. Counting the values makes it much larger, since each present field contributes all of its own values and they multiply. The meaningful count stays at three either way, so the gap only widens — presence is simply the cheapest way to state it in a review.
- What happens to the flat record when a seventh optional field is added?Permitted combinations double to one hundred and twenty-eight while meaningful shapes rise by at most one, and nothing in the type records which of the new combinations count. Every place that infers the payment kind from field presence now has one more field to ignore or to handle, and those places rarely get updated together.
- Is there a case where a record of optional fields genuinely models the domain?Yes — when the fields really are independent, such as optional contact details where any subset is legitimate. The test is whether the domain forbids some combinations. If every combination is meaningful, the product is the honest shape; if only a few are, the type is claiming more than the domain allows.
saying these in an interview costs you the question
- Argues variants are simply cleaner, with no account of what each shape permits
- Claims a record of optional fields carries the same information as a closed choice
- Thinks adding an optional field costs one more case rather than doubling the combinations
- Converts every optional field into a variant, including genuinely independent ones
- Assumes a validator makes the flat record equivalent to the choice for a reader