TypeScript Branded Types
TypeScript uses a structural type system. This means if two objects have the same shape, they are considered compatible. However, sometimes you want to distinguish between two types that have the same underlying structure (e.g., a UserId string and an Email string) to prevent logic errors.
Branded Types (also called Opaque Types or Nominal Typing) allow you to create types that are distinct from their underlying primitives.
1. The Problem: Structural Typing
type UserId = string;
type PostId = string;
function deletePost(postId: PostId) { /* ... */ }
const myUserId: UserId = "user_123";
const myPostId: PostId = "post_456";
// DANGER: TypeScript allows this because both are just 'string'
deletePost(myUserId);
2. Creating a Branded Type
To fix this, we intersect the primitive type with an object containing a unique "brand" property. This property doesn't actually exist at runtime, but it tricks the compiler.
// Define the Brand
type Brand<K, T> = K & { __brand: T };
// Define Branded Types
type UserId = Brand<string, "UserId">;
type PostId = Brand<string, "PostId">;
// Usage
function deletePost(postId: PostId) {
console.log("Deleting post", postId);
}
const myUserId = "user_123" as UserId;
const myPostId = "post_456" as PostId;
deletePost(myPostId); // OK
// Error: Argument of type 'UserId' is not assignable to parameter of type 'PostId'.
// deletePost(myUserId);
3. Validation Functions (Type Guards)
You shouldn't just cast (as) blindly. Branded types are perfect for ensuring data has passed a validation check.
type Email = string & { __brand: "Email" };
function isEmail(input: string): input is Email {
return input.includes("@");
}
function sendWelcomeEmail(email: Email) {
console.log("Sending to", email);
}
const input = "not-an-email";
if (isEmail(input)) {
// TypeScript knows 'input' is 'Email' here
sendWelcomeEmail(input);
} else {
// sendWelcomeEmail(input); // Error
}
4. Runtime Impact
Branded types are a compile-time only construct. The __brand property is never added to the object at runtime. The compiled JavaScript just sees plain strings or numbers. This means there is zero runtime overhead.
programming/javascript/typescript/typescript programming/javascript/typescript/intersection-types