Branded Types in TypeScript

Branded Types in TypeScript

Branded types provide deeper specificity and uniqueness on top of primitive types, enabling compile-time checks that prevent mixing incompatible values.1

The Problem

When you model entities with primitive types, TypeScript treats all values of the same primitive as interchangeable. For example:

type User    = { id: string; name: string }
type Post    = { id: string; ownerId: string; comments: Comment[] }
type Comment = { id: string; authorId: string; body: string }

async function getCommentsForPost(postId: string, authorId: string) { /* ... */ }
const comments = await getCommentsForPost(user.id, post.id) 
// No compile-time error—even though arguments are swapped

Because User["id"], Post["id"], and Comment["id"] are all just string, TSC can’t catch mismatches at compile time.

Naive Branded Type Implementation

A common community pattern is to “tag” a primitive with a brand:

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

type UserID    = Brand<string, "UserId">
type PostID    = Brand<string, "PostId">
type CommentID = Brand<string, "CommentId">

async function getCommentsForPost(postId: PostID, authorId: UserID) { /* ... */ }
const comments = await getCommentsForPost(user.id, post.id) 
// ❌ TypeScript now errors on swapped IDs

Downsides of this naive approach

  • The __brand property is visible in Intellisense and can be misused (it isn’t present at runtime).
  • Nothing prevents duplicate brands (multiple Brand<string, "X"> could collide).
  • The tag is purely compile-time and may confuse developers.

Improved Branded Type Utility

A stronger implementation hides the brand key and enforces uniqueness:

// Brand.ts
declare const __brand: unique symbol

type Brand<B> = { [__brand]: B }
export type Branded<T, B> = T & Brand<B>
  • Uses a unique symbol to prevent collisions.
  • Keeps the property key inaccessible at runtime and in Intellisense.

Comparison of Implementations

ImplementationDefinitionDownsides
Naivetypescript<br>type Brand<K, T> = K & { __brand: T }<br>Intellisense noise; duplicate brands; runtime invisibility
Improvedtypescript<br>declare const __brand: unique symbol<br><br>type Brand<B> = { [__brand]: B }<br>export type Branded<T, B> = T & Brand<B><br>Hides brand key; enforces uniqueness; no collisions

Why Use Branded Types?

  • Clarity: Express intent—e.g., Username vs. raw string.
  • Safety & Correctness: Catch mismatches (ID swaps, unit errors) at compile time.
  • Maintainability: Communicate data roles more clearly and simplify refactoring.

Use Cases

1. Custom Validation

type EmailAddress = Branded<string, "EmailAddress">

function validateEmail(email: string): EmailAddress {
  // validation logic…
  return email as EmailAddress
}

2. Domain Modeling

type CarBrand   = Branded<string, "CarBrand">
type EngineType = Branded<string, "EngineType">

function createCar(brand: CarBrand, engine: EngineType) { /* … */ }

3. API Responses & Requests

type ApiSuccess<T> = T & { __apiSuccess: true }
type ApiFailure   = { code: number; message: string; error: Error } & { __apiFailure: true }
type ApiResponse<T> = ApiSuccess<T> | ApiFailure

Now you can write precise type guards and avoid mishandling responses.

Challenge

Create a branded Age type bounded to [0, 125]:

  1. Define type Age = Branded<number, "Age">.
  2. Implement createAge(input: number): Age that throws if out of range.
  3. Implement getBirthYear(age: Age): number.

Visualization

graph LR A[Define Brand Utility] --> B[Create Branded Types] B --> C[Use in Function Signatures] C --> D[Compile-Time Errors on Mismatch] D --> E[Safer, Clearer Code]

Conclusion

Branded types are a lightweight way to introduce nominal typing into TypeScript. They enhance type safety, improve code clarity, and reduce runtime errors by making incompatible values unassignable at compile time. Use them judiciously to model domain concepts, validate inputs, and handle API contracts with confidence.


Footnotes

  1. https://egghead.io/blog/using-branded-types-in-typescript "Improve Runtime Type Safety with Branded Types in TypeScript"˄


Backlinks