TypeScript Snippet

Branded (Nominal) Types

Difficulty: Medium

TypeScript is structurally typed, so `type UserId = string` and `type OrderId = string` are interchangeable to the compiler, which is exactly the bug class you want to prevent. Branded (nominal) types attach a phantom tag so the two stay distinct at compile time without any runtime cost. This snippet covers the basic brand pattern, a parser-style smart constructor that produces a brand from a raw value, and a multi-brand domain model that ties them together.

Code Snippets
/

Branded (Nominal) Types

Branded (Nominal) Types

TypeScript is structurally typed, so `type UserId = string` and `type OrderId = string` are interchangeable to the compiler, which is exactly the bug class you want to prevent. Branded (nominal) types attach a phantom tag so the two stay distinct at compile time without any runtime cost. This snippet covers the basic brand pattern, a parser-style smart constructor that produces a brand from a raw value, and a multi-brand domain model that ties them together.

TypeScript
Medium
3 snippets
ts-utility-types
ts-type-narrowing
ts-generics
code-template

705 views

20

The trick is the intersection with { readonly __brand: B }: the structural part stays compatible with the underlying primitive, but the phantom property makes two brands incompatible because their __brand literal types differ. The __brand field never exists at runtime; it is purely a compile-time marker. Wrapping construction in asUserId is the convention so the cast happens in exactly one place. After that point, loadUser cannot accept a raw string or an OrderId, which catches the entire family of mixed-up-id bugs.