What are Branded Types? (And When You Should Use Them) in TypeScript

Date: 2025-06-04 | build | craft | create | programming | static-types | tech | types | typescript |

DISCLOSURE: If you buy through affiliate links, I may earn a small commission. (disclosures)

I recently came across TypeScript's Branded Types at work and thought they were a useful tool for getting more value out of TypeScript's type system.

Here we'll dive into what TypeScript Branded Types are, how to use them, and when it might be useful.

TypeScript Types

First let's talk about TypeScript's types and why Branded Types are even useful.

TypeScript uses structural typing which basically means if the structure of one type matches the expected structure then it's accepted. This works fine in many cases and is the default for many langs like Go and Python.

The issue occurs when you want to tell two different types that look the same apart.

This is relatively common with primitive types like string fields although this can also happen with more complex types.

type UserId = string 
type OrderId = string 

const getUserInfo = (userId: UserId): string => {
    return userId as string
}

const myUserId = "myuserid" as UserId
const myOrderId = "myorderid" as OrderId

console.log("UserId: ", getUserInfo(myUserId))
console.log("OrderId: ", getUserInfo(myOrderId)) // Due to structural typing - this is allowed

TypeScript doesn't see any difference between UserId and OrderId types as they're both just strings so the type system won't protect you from passing one type into a function expecting the other.

TypeScript Branded Types

Branded types provides a bit of extra info on the type which helps TypeScript understand that these types are actually unique.

This extra info is stripped at compile time so there's no runtime hit to performance.

We can add brands to the type by adding a readonly __brand field. We then use unique symbol so that the compiler knows these are unique - if you put the same value in two types that are structurally the same it will still think they're the same.

type UserIdBranded = UserId & { readonly __brand: unique symbol }
type OrderIdBranded = OrderId & { readonly __brand: unique symbol }

const getUserInfoBranded = (userId: UserIdBranded): string => {
    return userId as string
}

const myBrandedUserId = "mybrandeduserid" as UserIdBranded
const myBrandedOrderId = "mybrandedorderid" as OrderIdBranded

getUserInfoBranded(myBrandedUserId)
// getUserInfoBranded(myBrandedOrderId) // Fails
// getUserInfoBranded(myUserId) // Fails 
// getUserInfoBranded(myOrderId) // Fails

When to use Branded Types

Branded Types are useful whenever you want to differentiate one type from another. This plays particularly well with the parse don't validate paradigm where you try to leverage the type system to prove that a type is valid.

An example of doing this might look like:

const tryParseValidUserId = (userId: string): UserIdBranded | null => {
    if(userId === "iamnotarealuserid") {
        return null
    }

    return userId as UserIdBranded 
}

const userIdToBrand = tryParseValidUserId(myUserId)
if(userIdToBrand != null) {
    getUserInfoBranded(userIdToBrand)
}

Other ways to brand types

The branding provided here is a bit tedious so I've found a few different ways to make this easier.

type Brand<K, T> = K & { __brand: T }

type MyType = Brand<string, "MyType">

Example code:

type Duration = {
    start: number 
    end: number 
}

const getDurationMs = (duration: Duration) => {
    return duration.end - duration.start 
}

const myDuration = {
    start: 0,
    end: 1
}

const myInvalidDuration = {
    start: 3, 
    end: 2
}

getDurationMs(myDuration)
getDurationMs(myInvalidDuration)

type Brand<K, T> = K & { __brand: T }

type ValidDuration = Brand<Duration, "ValidDuration"> 

const tryGetValidDuration = (duration: Duration): null | ValidDuration => {
  if(duration.end < duration.start) {
    return null 
  }

  return duration as ValidDuration
}

const getDurationMsValid = (duration: ValidDuration) => {
    return duration.end - duration.start 
}

// getDurationMsValid(myDuration) // Not allowed

const myDurationValidated = tryGetValidDuration(myDuration)
if(myDurationValidated) {
  console.log("Duration is validated: ", getDurationMsValid(myDurationValidated))
}

const myInvalidDurationValidated = tryGetValidDuration(myInvalidDuration)
if(myInvalidDurationValidated) {
  // We should not get here
  console.log("Invalid duration is validated: ", getDurationMsValid(myInvalidDurationValidated))
}

Next

TypeScript remains one of my favorite programming languages in 2025 largely due to its ecosystem and type system. Branded Types help me leverage the type system just a bit better.

If you liked this post you might also like:

Want more like this?

The best way to support my work is to like / comment / share for the algorithm and subscribe for future updates.