Siddhant DevalAuthor
Senior Full-Stack Engineer·Aug 27, 2026·15 min read
Advanced Patterns: Variance, Branded Types & Exhaustiveness
The ceiling of TypeScript mastery is encoding business rules into the type system so that illegal states are literally unrepresentable — not documented, not guarded at runtime, but impossible to construct. Variance, branded types, and exhaustive unions are the three tools that get you there.
Technical Series
TypeScript Mastery
Part 10 of 11
Advanced Patterns: Variance, Branded Types & Exhaustiveness
Types are a specification language — not an annotation layer. The ceiling of that specification language is encoding business rules so that illegal states are literally unrepresentable — not documented in a README, not guarded at runtime, but impossible to construct at the type level. Variance tells you which substitutions are safe. Branded types prevent semantic confusion between structurally identical values. Exhaustive unions ensure that adding a new case always surfaces as a compile error at every handling site. Together, these three patterns are what distinguishes a TypeScript codebase that uses types versus one that is designed with types.
1. Variance — Covariance and Contravariance
Variance describes how a generic type's assignability changes as its type parameter changes. There are two directions that matter for TypeScript engineers:
- Covariant (output/return positions): If
Dog extends Animal, then() => Dogis assignable to() => Animal. The type narrows with the type parameter. - Contravariant (input/parameter positions): If
Dog extends Animal, then(x: Animal) => voidis assignable to(x: Dog) => void. The type widens with the type parameter.
1.1 Why Function Parameters Are Contravariant
typescript
typescript
Crucial Requirement
strictFunctionTypes: true (part of strict) enables correct contravariant checking for function types in non-method positions. Method positions (obj.method(x: Dog)) remain bivariant for backward compatibility — this is a known TypeScript trade-off documented in the specification.
Expand
2. Branded / Nominal Types
TypeScript's structural system means
type UserId = string and type OrderId = string are completely interchangeable. When they represent semantically distinct concepts, that silent interchangeability creates bugs that the compiler cannot catch.2.1 The Semantic Mix-Up Problem
typescript
2.2 The Brand Pattern
typescript
Architectural Note
The
__brand property is never present at runtime — it exists only in the type system. The as UserId cast inside the smart constructor is the only cast required, and it is safely encapsulated. All consuming code is free of casts.
Expand
3. Phantom Types
A phantom type carries type information that has no runtime representation — useful for encoding state machines into the type system:
typescript
4. Result<T, E> — Typed Error Handling Without Exceptions
The
Result pattern models success and failure as a discriminated union, making the error channel explicit in the type signature:typescript

Expand
5. The using Keyword (TypeScript 5.2)
using implements the Explicit Resource Management proposal (TC39 Stage 4). It calls Symbol.dispose on the resource at the end of the block — the TypeScript equivalent of RAII:typescript
6. Const Enums vs. Union Types
| Pattern | Tree-shakeable | isolatedModules | Runtime value | Recommendation |
|---|---|---|---|---|
const enum | ✅ (inlined by tsc) | ❌ Incompatible | Inlined literal | Avoid — breaks with Vite/esbuild |
enum | ❌ Generates an object | ✅ Compatible | Object property | Avoid — non-tree-shakeable, generates JS |
| Union type | ✅ Erased | ✅ Compatible | None (erased) | Prefer for type-only enumerations |
const object | ✅ (if unused) | ✅ Compatible | Object | Prefer when runtime access is needed |
typescript
Summary
| Concept | Rule |
|---|---|
| Covariance | Return/output positions — Dog → Animal direction (subtype is assignable to supertype) |
| Contravariance | Parameter/input positions — Animal → Dog direction for handler substitution |
strictFunctionTypes | Enables correct contravariant checking for function types in non-method positions |
| Branded types | T & { __brand: 'X' } — creates structural incompatibility for semantically distinct values |
| Smart constructor | Contains the one as BrandedType cast; all consumers receive a properly typed value |
| Phantom types | Encode state machine transitions in the type system — zero runtime overhead |
Result<T, E> | Typed error channel — the ok discriminant drives exhaustive CFA without exceptions |
using keyword | TS 5.2+ — calls Symbol.dispose at block exit; deterministic cleanup without try/finally |
| Const enums | Avoid with Vite/esbuild — use union types or const objects instead |
What's Next
In Part 11, the final article, we cover TypeScript with React — generic components,forwardRef, typeduseReducer, and context typing. These are the patterns that eliminate the dailyascasts from every React codebase.
Research & Synthesis Note
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.
#TypeScript#Variance#Branded Types#Exhaustiveness#Advanced TypeScript