A union type models an OR relationship: a value is one case from a fixed set, rather than several values that all exist together. In F#, discriminated unions fill the domain-modeling gap that records and tuples leave open, while records and tuples model AND relationships. section 8.3 section 5.7
Use this comparison to choose a record or tuple when values coexist, and a union when exactly one alternative can exist. section 8.3
Declare named cases, with each case carrying only the data it needs. Case data can be a primitive, tuple, record, anonymous record, or another union. The case names act as lightweight functions that construct values of the enclosing union type. section 8.3.1
Contact methods
A customer can have a name, age, and contact method, but the contact method is Email with an address, Telephone with country and number, or Post with address details. Creating Email requires only an address; creating Telephone requires only its country and number. All are ContactMethod values, not separate Email or Telephone types. section 8.3.1
Consume a union with pattern matching. Each branch identifies one case and safely binds that case’s data, so telephone details cannot be read from an email value. A match also keeps behavior together: adding a behavior means adding a function over the cases, whereas adding a case requires updating its matches. section 8.3.2
Use unions to make domain states explicit:
- Nest unions when an alternative has further alternatives, such as an SMS contact holding either a local or international telephone number. section 8.3.4
- Replace vague flags and numbers with bounded cases such as YearsAsCustomer, OverdraftStatus, and LoanDecision. section 8.3.4
- Represent validation failures as named error cases when callers must distinguish and reason about exact errors. section 9.2.3
- Use a single-case union to distinguish otherwise identical primitive fields and add domain richness. section 8.4.3
Prefer explicit branches over a broad wildcard when completeness matters. With bounded cases, the compiler warns about an omitted case—even inside nested matches—whereas a wildcard can silently absorb a newly added case. This protection can remove the need to test merely whether every union case was handled. section 8.3.4 section 14.1.1
Keep unions as data and place operations in functions or modules. Combined with records, tuples, lists, options, and results, they let a model express both coexisting data and controlled alternatives; custom unions are especially useful for tightly defined error domains. section 10.1.2 section 9.2.6









