TypeScript
What is a branded type?
Learn nominal-like typing with branded types.
What is a branded type?
TypeScript has a structural type system; two types with the same shape are mutually assignable. So type UserId = string and type OrderId = string are actually the same type and can be misused interchangeably. A branded type adds an invisible compile-time marker that makes these types structurally distinct, providing safety similar to nominal type systems.
type UserId = string;
type OrderId = string;
const uid: UserId = "u1";
const oid: OrderId = uid; // structural typing: valid but risky
Nominal-like safety with brands
The brand pattern makes a base type structurally distinct by intersecting it with a marker. The marker field does not exist at runtime; it only distinguishes types at compile time. Thus UserId and OrderId cannot be assigned to each other even though they share the same base type.
type Brand<T, B> = T & { readonly __brand: B };
type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
const uid = "u1" as UserId;
const oid: OrderId = uid; // error: types differ
Exercise Separate two types with brands
Define the Brand<T, B> generic helper type using intersection. Use it to create UserId and OrderId types (both string-based). Create uid from "u1" as UserId, and oid from "o1" as OrderId via type assertion. Print both values with console.log.
Show example solution
type Brand<T, B> = T & { readonly __brand: B };
type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
const uid = "u1" as UserId;
const oid = "o1" as OrderId;
console.log(uid);
console.log(oid);
Test yourself
-
For the definitions type UserId = string and type OrderId = string, which of the following is true?
- UserId accepts only string values starting with the 'u' prefix
- UserId and OrderId are structurally the same, so they are mutually assignable
- UserId and OrderId cannot be assigned because they have different names
- The TypeScript compiler compares names
Show answer
UserId and OrderId are structurally the same, so they are mutually assignable — TypeScript compares structural types by shape, not by name; since both aliases are string, they are structurally equivalent and mutually assignable.
-
Brand marker
type Brand<T, B> = T & { readonly ____: B };
Show answer
type Brand<T, B> = T & { readonly __brand: B };
-
Branded type parts
- Yapısal tip sistemi
- same shape = assignable
- Nominal tip sistemi
- different name = not assignable
- Brand pattern
- makes structural types behave like nominal
- __brand alanı
- compile-time marker
Next lesson: Brand definition