Branded Type
Problem
Functions accept any string or number value, allowing invalid inputs like negative IDs, malformed email addresses, or mixing up user IDs with product IDs. TypeScript treats all strings and numbers as interchangeable, so passing a user ID where a product ID is expected compiles successfully but causes bugs at runtime.
Solution
Add a unique symbol or property to primitive types to create nominally distinct types that can’t be assigned to each other. This makes UserID
and ProductID
incompatible at compile time even though they’re both strings, catching mix-ups before runtime.
Example
This example demonstrates creating branded types for UserID and ProductID that are incompatible despite both being strings, preventing accidental misuse at compile time.
// Create branded types by intersecting with a unique brand property
type UserID = string & { readonly __brand: 'UserID' };
type ProductID = string & { readonly __brand: 'ProductID' };
// Function that only accepts UserID
function getUser(id: UserID) { /* ... */ }
// Create instances with type assertions
const userId = 'user-123' as UserID;
const productId = 'prod-456' as ProductID;
getUser(userId); // ✓ OK - UserID is expected
getUser(productId); // ✗ Type error - ProductID is not compatible with UserID
Benefits
- Prevents mixing incompatible values like user IDs and product IDs at compile time.
- Catches bugs before runtime by making primitives nominally distinct.
- Documents intent by making domain concepts explicit in the type system.
- No runtime overhead since brands are compile-time only constructs.
- Enables stronger validation and constraints on primitive values.
Tradeoffs
- Requires type assertions when creating branded values from raw primitives.
- Adds type complexity that can confuse developers unfamiliar with the pattern.
- Runtime validation is still needed since TypeScript types don’t exist at runtime.
- Can make it harder to work with third-party libraries expecting plain primitives.
- May require additional helper functions for creating and validating branded types.