TypeScript uses structural typing, also known as "duck typing". For type compatibility, the structure (signature) is important, not the name or origin of the type.
Example:
interface Point2D { x: number; y: number; } interface Coord2D { x: number; y: number; } // These types are interchangeable: Point2D and Coord2D, because the structure is the same. const foo: Point2D = { x: 1, y: 2 }; const bar: Coord2D = foo; // OK!
Nominal typing: for type compatibility, the "name" or "factory" is important, the structure is not.
In TypeScript, nominal typing is not natively supported, but it can be emulated using branded types:
type USD = number & { readonly __brand: unique symbol } type EUR = number & { readonly __brand: unique symbol } let priceUSD: USD; let priceEUR: EUR; // priceUSD = priceEUR; // Error! Different brands.
Why apply this? For example, to distinguish between structurally identical, but semantically different types — currencies, userID/tokenID, physical quantities, etc.
Question: Why does the following code compile without errors, even though Address and UserId are logically different types?
interface Address { value: string; } interface UserId { value: string; } let id: UserId = { value: "test" }; let addr: Address = id; // OK
Answer: Because in TypeScript, the structure is important, not the type name. Both types are just "an object with value: string".
History
Project: A financial system with calculations in USD/EUR. Amounts were passed as numbers. Once, the currencies were mixed up during addition — due to structural typing, TypeScript did not catch it. Later, branded types were introduced to exclude such errors at compile time.
History
Project: In the development of a REST API, objects were used for IDs of different entities (userId, groupId), both with a value: string field. By mistake, userId was substituted instead of groupId, and only the business logic on the server detected the error.
History
Project: In a parsing library for DSL, identical structures were used (type value = { kind: 'num'|'str', value: number|string }). Homogeneous structures got mixed between different parts of the code, leading to logical errors. Artificial brand fields were added for separation.