Nominal Typing in TypeScript
Last Updated :
23 Jul, 2025
TypeScript is a language that has a static type system and enhances JavaScript by adding descriptions of types. One key part of TypeScript’s type system is its support for structural typing.
But there are times when developers need more control over type identity, this is where the idea of nominal typing comes in place. In this article, we will learn more about Nominal Typing in TypeScript.
What is Nominal Typing?
Nominal typing is a type system where compatibility between types is based on explicit declarations rather than their structure. In simple words, two types are considered compatible only if they are explicitly declared to be the same, even if they have the same structure.
Nominal typing is not built into TypeScript natively, but it can be simulated using various techniques. Nominal typing is useful when you want to ensure that certain types are not mistakenly considered interchangeable, even if they have similar structures.
Why Use Nominal Typing?
Nominal typing helps to:
- Prevent Accidental Assignments: It doesn’t let us use different types inadvertently whenever we mean them to represent separate concepts.
- Improve Code Safety: It enforces that only the intended types are used in specific contexts.
- Enhance Readability: The code readability, maintainability become better when we know the type of a variable.
Implementing Nominal Typing in TypeScript
However, TypeScript itself does not offer nominal typing out of the box but through methods such as branding or unique symbols you can work around this.
1. Using Branding
Branding is a technique where you create a unique marker or tag to distinguish types, this is done using a unique symbol, or an interface with a private property.
Example: In this example, UserId and OrderId differ only in their brands although both are strings, this prevents their interchangeability thus enhancing type safety.
JavaScript
// Define a unique brand for the type
interface UserIdBrand {
_brand: 'UserId';
}
interface OrderIdBrand {
_brand: 'OrderId';
}
// Define types that use branding
type UserId = string & UserIdBrand;
type OrderId = string & OrderIdBrand;
// Function to create branded types
function createUserId(id: string): UserId {
return id as UserId;
}
function createOrderId(id: string): OrderId {
return id as OrderId;
}
// Example usage
const userId = createUserId('user123');
const orderId = createOrderId('order456');
// This will result in a compile-time error
// const invalidId: UserId = orderId; // Error: Type 'OrderId' is not assignable
to type 'UserId'.
console.log(`User ID: ${userId}`);
console.log(`Order ID: ${orderId}`);
Output
User ID: user123
Order ID: order456
2. Using Unique Symbols
You can also use unique symbols to enforce nominal typing. This method involves defining a unique symbol for each type.
Example: In this example UserId and OrderId have different primitive values even though they are technically objects with the same structure.
JavaScript
// Define unique symbols for nominal typing
const UserIdSymbol = Symbol('UserId');
const OrderIdSymbol = Symbol('OrderId');
// Define types that use unique symbols
type UserId = { __symbol: typeof UserIdSymbol };
type OrderId = { __symbol: typeof OrderIdSymbol };
// Functions to create nominal types
function createUserId(): UserId {
return { __symbol: UserIdSymbol };
}
function createOrderId(): OrderId {
return { __symbol: OrderIdSymbol };
}
// Example usage
const userId = createUserId();
const orderId = createOrderId();
// This will result in a compile-time error
// const invalidId: UserId = orderId; // Error: Type 'OrderId' is not assignable
to type 'UserId'.
console.log(`User ID Symbol: ${userId.__symbol.toString()}`);
console.log(`Order ID Symbol: ${orderId.__symbol.toString()}`);
Output
User ID Symbol: Symbol(UserId)
Order ID Symbol: Symbol(OrderId)
3. Using Classes
Creating distinct class types through the classes in TypeScript is another way to achieve nominal typing, instances of different classes are not even though similar structures exist, considered to be of the same type hence making accidental assignments impossible.
By specifying various types with their respective constructors, it is possible to create nominally distinct ones and even when two kinds have equal properties they can not be interchanged in TypeScript.
Example: Below is the example demonstrating use of classes in Nominal Typing in Typescript.
JavaScript
class UserId {
constructor(public value: string) { }
}
class OrderId {
constructor(public value: string) { }
}
// Example usage
const userId = new UserId('user123');
const orderId = new OrderId('order456');
// This will result in a compile-time error
// const invalidId: UserId = orderId; // Error: Type 'OrderId' is not assignable
to type 'UserId'.
console.log(`User ID: ${userId.value}`);
console.log(`Order ID: ${orderId.value}`);
Output
User ID: user123
Order ID: order456
4. Using Tagged Unions
Nominal typing can also be implemented using so-called tagged unions or discriminated unions, by having a shared discriminating property on union types, every variant is treated as if it were a separate type.
Tagged unions uses an assortment of objects that have diverse tags to create a type, this tag acts as a nominal identifier which prevents interchangeability between the types.
Example: Below is the example demonstrating use of Tagged Unions in Nominal Typing in Typescript.
JavaScript
type UserId = { type: 'UserId'; id: string };
type OrderId = { type: 'OrderId'; id: string };
function printId(id: UserId | OrderId): void {
if (id.type === 'UserId') {
console.log(`User ID: ${id.id}`);
} else if (id.type === 'OrderId') {
console.log(`Order ID: ${id.id}`);
}
}
// Example usage
const userId: UserId = { type: 'UserId', id: 'user123' };
const orderId: OrderId = { type: 'OrderId', id: 'order456' };
// This will result in a compile-time error
// const invalidId: UserId = orderId; // Error: Type 'OrderId' is not assignable
to type 'UserId'.
printId(userId);
printId(orderId);
Output
User ID: user123
Order ID: order456
5. Using Branded Classes
In this method, you create a class for each distinct type and use a unique symbol or a private property as a brand to enforce type distinction. This combines the benefits of classes with branding to achieve nominal typing.
Example: Here’s how you can implement nominal typing using branded classes:
JavaScript
const UserIdBrand = Symbol('UserIdBrand');
const OrderIdBrand = Symbol('OrderIdBrand');
class UserId {
private readonly _brand = UserIdBrand;
constructor(public value: string) { }
}
class OrderId {
private readonly _brand = OrderIdBrand;
constructor(public value: string) { }
}
function createUserId(value: string): UserId {
return new UserId(value);
}
function createOrderId(value: string): OrderId {
return new OrderId(value);
}
const userId = createUserId('user123');
const orderId = createOrderId('order456');
console.log(`User ID: ${userId.value}`);
console.log(`Order ID: ${orderId.value}`);
Output:
User ID: user123
Order ID: order456
Similar Reads
TypeScript Structural Typing TypeScript type system is based on structural typing, therefore, TypeScriptâs type compatibility is determined by the structure (i.e., the properties and methods) rather than explicit declarations or names. In this article, we will learn in detail about TypeScript Structural Typing.What is Structura
5 min read
TypeScript Tutorial TypeScript is a superset of JavaScript that adds extra features like static typing, interfaces, enums, and more. Essentially, TypeScript is JavaScript with additional syntax for defining types, making it a powerful tool for building scalable and maintainable applications.Static typing allows you to
8 min read
TypeScript Writing Good Overloads In this article, we are going to learn about Writing Good Overloads in Typescript. TypeScript is a popular programming language used for building scalable and robust applications. For writing good overloads, you should always prefer parameters with union types instead of overloads when possible beca
3 min read
Whatâs new in TypeScript 5.4 ? TypeScript 5.4 brings forth several new capabilities and refinements, aiming to enhance developer productivity and code quality. Some of the new capabilities that are added or Introduced in TypeScript 5.4 are as follows: Table of Content Variadic Tuple TypesClass Property Inference from Constructors
2 min read
TypeScript Narrowing Assignments TypeScript narrowing assignments is a type of type inference mechanism that occurs when TypeScript examines the right side of an assignment and uses that information to narrow down the type of the variable or property on the left side of the assignment.Syntaxlet value: string | number = valueOftheVa
3 min read
TypeScript Defining a Union Type In this article, we are going to learn about Defining a Union Type in Typescript. TypeScript is a popular programming language used for building scalable and robust applications. In TypeScript, a union type allows a variable to have one of several possible types. You can define a union type by using
3 min read