@@ -0,0 +1,137 @@
# Branded Types in TypeScript
Branded types (also known as _ opaque types _ ) are a TypeScript pattern used to make primitive types like `string` or `number` more specific and safer.
They allow you to distinguish between values that are structurally identical but conceptually different — for example, an `Email` vs a regular `string` .
## The Core Idea
We create a "brand" by intersecting a base type (`string` ) with an object containing a unique marker:
``` ts
type Branded < T , B extends string > = T & { readonly __brand : B } ;
```
- `T` → the base type (e.g., `string` )
- `B` → a unique label (e.g., `"Email"` )
This creates a new type that behaves like `T` but is **not interchangeable ** with other branded types.
## Defining a Branded Type
``` ts
type Email = Branded < string , "Email" > ;
```
Now `Email` is no longer just a `string` — it’ s a _ distinct type _ .
## Creating Values Safely
To construct a branded value, you typically use a factory function:
``` ts
function createEmail ( value : string ) : Email {
if ( ! value . includes ( "@" ) ) {
throw new Error ( "Invalid email" ) ;
}
return value as Email ;
}
```
### Why use a function?
- Centralizes validation
- Prevents unsafe casting (`as Email` ) scattered across the codebase
- Ensures only valid values become branded types
## Example
``` ts
const email = createEmail ( "test@example.com" ) ;
function getEmail ( email : Email ) {
console . log ( email ) ;
}
getEmail ( email ) ;
```
## Invalid Usage
``` ts
const email = createEmail ( "" ) ; // Throws "Invalid email"
```
``` ts
getEmail ( "plain string" ) ; // Type error
```
Even though `"plain string"` is a `string` , it is **not ** an `Email` .
## What Makes It Work?
This line is the key:
``` ts
T & { __brand : B } ;
```
It creates an **intersection type ** , meaning:
- At runtime -> it's still just a `string`
- At compile time -> TypeScript treats it as a unique type
So this fails:
``` ts
type UserId = Branded < string , "UserId" > ;
const email : Email = createEmail ( "a@b.com" ) ;
const userId : UserId = email ; // Type error
```
Even though both are strings, their brands differ.
## Important Limitation
Branded types exist **only at compile time ** .
``` ts
typeof em === "string" ; // true
```
There is **no runtime difference ** between `Email` and `string` .
## When to Use Branded Types
Use them when:
- You have multiple IDs (`UserId` , `OrderId` , etc.)
- You want stronger domain modeling
- You want to prevent mixing similar primitives
- You want safer APIs without runtime overhead
## Final Example (Complete)
``` ts
type Branded < T , B extends string > = T & { readonly __brand : B } ;
type Email = Branded < string , "Email" > ;
function createEmail ( value : string ) : Email {
if ( ! value . includes ( "@" ) ) {
throw new Error ( "Invalid email" ) ;
}
return value as Email ;
}
const email = createEmail ( "damn@example.com" ) ;
function getEmail ( email : Email ) {
console . log ( email ) ;
}
getEmail ( email ) ;
```
[Readmore ](https://dev.to/themuneebh/typescript-branded-types-in-depth-overview-and-use-cases-60e )