1 Definition and Motivation
A tagged union is a composite data type designed to hold exactly one value chosen from a set of possible variants. Alongside the stored value, the data carries a “tag” (a discriminator) that identifies which variant is currently present. Code that consumes a tagged union typically begins by inspecting the tag, ensuring that the payload is interpreted using the correct variant’s rules.
Tagged unions are often used as a “safe alternative” to representing multiple conceptual cases with a single loosely typed field. In strongly typed languages, they help prevent invalid interpretations—such as treating an “error payload” as if it were a “successful result payload.”
1.1 What “union” means in data modeling
In this context, “union” refers to a type that can represent several alternative shapes or meanings. Unlike a structure that has a fixed set of fields, a union-like representation allows the stored data to conform to one of several variant definitions at a time.
In practice, the union serves as a compact model for “one-of-N” cases, such as different message formats, request types, or geometric shapes.
1.2 The role of the “tag”
The tag is a value that encodes which variant is active. It can be an integer, an enumeration member, or a small symbolic value. The tag’s key property is that it is used as the authority for how the payload should be read.
For example, a tagged union representing a shape might use one tag value to indicate a circle variant and a different tag value to indicate a rectangle variant. The payload then stores the corresponding parameters for that case.
1.3 How tagged unions improve safety and clarity
By requiring the variant identity to be explicit, tagged unions reduce the likelihood of mismatched logic. Instead of relying on conventions like “if field X is non-null, interpret it as case Y,” the program’s control flow can align directly with the tag.
They also improve readability: the data type communicates that it contains alternatives, and the consuming code typically mirrors that intent through branching or pattern matching constructs.
2 Core Structure
A tagged union consists of (1) a tag field and (2) storage for the payload of each variant. Conceptually, the payload is only valid for the currently selected variant as indicated by the tag.
Although implementations differ, the semantics aim to enforce two things: the tag must correspond to a single variant, and the payload must satisfy the variant’s expected shape.
2.1 Tag field
The tag field identifies the chosen variant. It is often constrained to a finite set of values, each associated with exactly one variant constructor in the type definition.
The tag may be directly stored as an enum-like value, or it may be represented implicitly through a layout scheme (though explicit tags are more common for clarity and interoperability).
2.2 Variant payloads
The payload portion holds the data associated with the active variant. Depending on the design, payload storage can be inline (embedded) or indirect (via pointers or references).
Each variant defines its own payload type, allowing variant-specific fields to be represented without forcing all cases into a single oversized structure.
2.3 Invariants and validity rules
A tagged union typically maintains invariants such as:
- The tag is always set to one of the defined variant identifiers.
- The payload satisfies the constraints of the indicated variant.
- Exactly one variant is considered “active” at any time.
These invariants are crucial for safe decoding and for preventing undefined behavior that would arise if the payload were interpreted using the wrong variant schema.
2.1.1 One-of-many representation
Tagged unions implement a one-of-many model where the union value carries one selected alternative rather than simultaneously carrying all alternatives.
This is different from designs that keep multiple optional fields and attempt to infer which one is meaningful.
2.1.1.1 Mapping tag values to variants
A standard way to define the mapping is to associate each tag value with a specific variant constructor. When constructing a value, the implementation sets the tag to the constructor’s identifier and stores the payload accordingly.
When consuming the union, the program checks the tag and then uses the associated payload interpretation.
2.2 Memory/layout considerations
Layout choices affect both performance and memory footprint. Common patterns include storing:
- The tag plus an inlined region large enough for the largest payload.
- The tag plus a set of pointers, one per variant (with only one pointer populated).
- The tag plus a discriminated “struct of fields” where only certain fields are valid.
The chosen layout impacts cache behavior, alignment, and whether payload data can be copied efficiently.
2.3 Construction of tagged union values
Construction typically follows a “constructor per variant” model. Each constructor:
- Sets the tag to the appropriate discriminant.
- Writes the payload data in the format required for that variant.
- Ensures that any remaining payload storage is either unused or treated as invalid.
Well-designed libraries also provide direct constructors for common cases (e.g., success vs. error) to reduce boilerplate and prevent inconsistent tag/payload combinations.
3 Type System Connections
Tagged unions relate closely to several well-known constructs in typed programming language theory and practice. They serve as a practical mechanism for representing sums of types (alternatives).
Many languages differ in syntax and runtime strategy, but they share the same core idea: encode choice and enforce correct interpretation through the tag.
3.1 Sum types (algebraic data types)
In type theory, a sum type represents a choice between multiple types. Each option is like a variant constructor, and the overall type is the “sum” of them.
Tagged unions are a common concrete representation of sum types, enabling the runtime to carry sufficient information for pattern matching and safe access.
3.2 Relation to discriminated unions
“Discriminated union” is often used as a near-synonym for tagged union. The “discriminator” refers to the same concept as the tag: the value used to decide which variant is active.
Different ecosystems may emphasize naming conventions and compile-time checks, but the underlying model is the same.
3.3 Relation to enums with associated data
Some languages provide enums whose variants carry associated data. Such enums can be viewed as tagged unions implemented through an enumeration-like syntax.
The tag is the enum variant identity, while the associated data corresponds to the payload. This gives a compact, readable way to model alternatives without defining a separate union abstraction.
4 Operations and Usage Patterns
Tagged unions are primarily used through operations that inspect the tag and react accordingly. In strongly typed settings, the goal is for the compiler to help ensure that each case is handled consistently.
Typical usage patterns include case analysis, converting between related representations, and validating data at boundaries such as parsing.
4.1 Pattern matching / case analysis
Pattern matching (or switch-like case analysis) branches based on the tag. Each branch binds the payload to the correct variant-specific structure.
This allows code to be written in a style that mirrors the logical structure of the data, reducing the risk of forgetting to handle a variant.
4.2 Type narrowing based on the tag
In type systems with advanced control-flow analysis, checking the tag enables “type narrowing.” After the tag check, the compiler can treat the union value as having the corresponding variant payload type.
This eliminates the need for repeated manual casts and can provide better tooling support such as autocompletion and error checking.
4.3 Converting between related representations
Tagged unions often interact with other common representations, such as options (presence/absence) and result-like outcomes. Conversions may restructure variants, translate tags, or wrap/unwrap payload types.
Care must be taken to ensure that invalid tag/payload combinations are not introduced during transformation.
4.1 Validation at boundaries (parsing/input)
When reading external data, the incoming representation may not respect the union’s invariants. A conversion step can validate the tag-like field from input and confirm that required fields for the indicated variant are present and well-formed.
This validation prevents downstream logic from operating on malformed or inconsistent data, turning runtime hazards into controlled errors.
5 Examples (Language-Agnostic)
The following examples illustrate the concept without tying it to a particular programming language’s syntax. Each case shows how a tagged union encodes alternatives and how consumers use the tag to interpret the payload.
5.1 Modeling a “shape” with different variants
A shape union might include:
- Circle: payload contains a radius.
- Rectangle: payload contains width and height.
- Triangle: payload contains base and height.
The tag identifies which geometric interpretation applies, while the payload holds the corresponding dimensions. Consumer code can compute an area by selecting the appropriate formula per tag.
5.2 Modeling request/response alternatives
In a request/response model, a union can represent different message kinds:
- A request variant for “read,” carrying an address or identifier.
- A request variant for “write,” carrying both an address and data.
- A response variant for “success,” carrying returned content.
- A response variant for “failure,” carrying an error description.
The tag ensures that the receiver knows whether it should treat the payload as request parameters or response results.
5.3 Modeling success/error outcomes
A common outcome pattern uses two variants:
- Success: payload holds a value of type T.
- Error: payload holds an error descriptor.
The tag determines whether processing continues with the success value or whether control flow moves to error handling. This is frequently used to avoid ambiguous “null means error” schemes.
5.3.1 Handling “unknown” or default variants
Some systems include a variant for “unknown” or “unrecognized.” This is particularly useful when reading data from older versions or from external sources where new variants may appear that the current program does not explicitly support.
The consumer can then handle this case explicitly, often by logging, rejecting the message, or mapping it to a safe fallback.
6 Common Implementation Strategies
Implementations vary in how payload storage is represented and how memory is managed. The correct choice depends on constraints such as performance, interoperability, and ease of construction.
Despite differences, all strategies must preserve the invariant that the tag dictates the valid payload interpretation.
6.1 Struct-of-fields approach
One approach stores a single struct containing:
- A tag field.
- Fields for each possible variant payload, with unused fields ignored.
This method can be simple, especially when payloads are small or when the union is primarily used for clarity rather than tight memory control.
6.2 Enum-discriminator with payload storage
Another approach stores:
- A discriminator (tag) that identifies the active variant.
- A payload area designed to hold the data for variants, often using a union-like memory overlay internally.
This can reduce wasted space compared to a struct-of-fields layout, while still keeping the tag explicit for safe interpretation.
6.3 Pointer-based variant storage
Pointer-based designs store:
- A tag field.
- A pointer (or reference) to the payload object for the active variant, with null or absent pointers for others.
This can simplify handling of large or complex payloads and can allow variants to be polymorphic. However, it typically introduces allocation overhead and additional indirection during access.
6.3.1 Trade-offs: allocation vs. simplicity
Pointer-based strategies often trade runtime costs (allocation and indirection) for simpler representation of variant payloads with varying sizes.
Inline storage strategies tend to be more efficient for frequent operations but can require careful layout and copying semantics.
7 Practical Considerations
Real-world use of tagged unions involves more than choosing the concept; developers must consider compile-time guarantees, runtime behavior under errors, and data interchange requirements.
These considerations shape how robust and maintainable the resulting codebase becomes.
7.1 Exhaustiveness checking
Exhaustiveness checking verifies that all variants are handled in case analysis or pattern matching. Many languages or libraries provide this as a compile-time feature.
When enabled, it reduces the chance that new variants introduced later will silently bypass important logic.
7.2 Error handling and default cases
Not all tagged unions are guaranteed to be exhaustively handled, especially when dealing with external input. In such situations, a default branch may be included to handle unexpected tags.
Good practice is to make that default branch explicit—often producing a controlled error rather than continuing with assumptions.
7.3 Serialization and deserialization
Tagged unions often require an explicit representation in serialized form. Typically, serialization includes:
- The tag (so the receiver knows which variant to reconstruct).
- The payload fields relevant to that variant.
Careful design avoids ambiguity during deserialization and ensures that payload fields are not misinterpreted.
7.3.1 Versioning tagged unions over time
When evolving a tagged union, new variants may be added and some may be changed. Versioning strategies include:
- Backward-compatible encoding where unknown tags map to an “unknown” variant.
- Maintaining stable tag identifiers across versions.
- Allowing payload field evolution with defaults or optional fields.
Stable tag semantics are especially important because changing tag values can break older data compatibility.
8 Comparisons and Alternatives
Tagged unions compete with other representations that solve similar problems: modeling alternatives, enabling safe access, and maintaining program clarity.
Comparisons clarify when a tagged union is a better fit and when alternatives may be preferable.
8.1 Tagged union vs. untagged union
An untagged union stores data from multiple possible variants without an explicit tag. Correct interpretation then relies on external context or programmer discipline, increasing the risk of mismatched reads.
Tagged unions embed the variant identity into the data itself, which typically enables safer decoding and more reliable runtime behavior.
8.2 Tagged union vs. interface/class hierarchies
Object-oriented hierarchies can represent alternatives using subclasses and virtual methods. While this provides polymorphism, it may require dynamic dispatch and can spread variant logic across class implementations.
Tagged unions centralize variant handling in pattern matching or case analysis. This can improve traceability of logic paths and make exhaustiveness checks more direct.
8.3 Tagged union vs. dynamic typing
In dynamically typed systems, alternatives might be represented using generic values plus runtime checks. While flexible, this shifts correctness from compile time to runtime and can lead to late failures.
Tagged unions aim to move correctness earlier by making variant cases explicit and enabling type-directed narrowing.
9 Pitfalls and Best Practices
Despite their advantages, tagged unions can be misused. Common issues involve tag/payload inconsistencies, naming and evolution problems, and performance in heavily used code paths.
Good practices focus on maintaining invariants, ensuring clarity, and optimizing critical operations without sacrificing safety.
9.1 Mismatched tag/payload bugs
A principal failure mode is constructing or mutating a value so that the tag no longer corresponds to the payload. This can happen through manual memory operations, unsafe casting, or incomplete transformations.
Using constructor functions and avoiding direct field mutation helps prevent such inconsistencies.
9.2 Fragile tag schemes and naming
If tag identifiers are treated informally—such as depending on string formatting or unstable numeric values—data interchange and long-term maintenance can suffer.
Best practice is to use stable, well-documented tag identifiers and consistent naming conventions across the codebase and across serialization formats.
9.3 Performance considerations in hot paths
Tagged unions may introduce branching during pattern matching. In hot loops, excessive case checks can reduce throughput, particularly if the tag distribution is highly unpredictable.
9.3.1 Minimizing branching in pattern matching
Performance can be improved by:
- Using efficient pattern matching constructs provided by the language/compiler.
- Restructuring data flow so that tags are checked once and reused.
- Favoring inline storage when payload sizes are moderate and copying costs are acceptable.
The best approach depends on compiler optimizations and actual runtime profiles.
10 See Also (Related Concepts)
Tagged unions intersect with several related terms and programming constructs. These concepts often appear in similar contexts, such as sum types, pattern matching, and common outcome types.
10.1 Discriminant, discriminator, and discriminated union
These terms describe the tag-like mechanism that distinguishes which variant is active. A “discriminated union” emphasizes the discriminator’s role in guiding safe interpretation.
10.2 Pattern matching
Pattern matching is a control structure that examines the structure of data and selects the appropriate branch for each variant. Tagged unions pair naturally with pattern matching due to their explicit tags.
10.3 Algebraic data types and option/result types
Algebraic data types provide a broader framework for constructing composite types from sums and products. Option and result types are common uses of sum-type modeling for presence/absence and success/failure outcomes.