Skip to content

Branded Types: Safer IDs, Money, and Domain Values

Use branded (nominal-ish) types to prevent mixing IDs and domain values, and learn safer alternatives to ‘just cast it’.

Rajesh DhimanMarch 01, 20262 min read
On this page

What you’ll learn

  • Why TypeScript is structurally typed (and what that means for domain safety)
  • How branded types prevent mixing UserId with OrderId
  • Safer ways to create brands than as UserId

30‑second interview answer

TypeScript is structurally typed, so two string values are interchangeable even if they represent different concepts. Branded types add a phantom property to create nominal-ish distinctions like UserId vs OrderId. This prevents entire classes of bugs in large codebases, especially around IDs, currency, and validated values.


The problem: structural typing mixes meanings

type UserId = string;
type OrderId = string;
 
function loadUser(id: UserId) {}
 
const orderId: OrderId = "ord_123";
loadUser(orderId); // ✅ compiles (bad!)

Brand pattern

type Brand<T, B extends string> = T & { readonly __brand: B };
 
export type UserId = Brand<string, "UserId">;
export type OrderId = Brand<string, "OrderId">;

Now this fails:

function loadUser(id: UserId) {}
const orderId = "ord_123" as OrderId;
 
loadUser(orderId); // ❌ Type error

The dangerous part: as UserId

You can still lie:

const userId = "not-a-real-id" as UserId;

So the important engineering question is: how do we create branded values safely?


Safer construction: parse/validate at boundaries

function isUserId(s: string): s is UserId {
  return /^usr_[a-z0-9]+$/.test(s);
}
 
export function parseUserId(s: string): UserId {
  if (!isUserId(s)) throw new Error("Invalid UserId");
  return s;
}
 
const id = parseUserId("usr_ab12"); // UserId

This is what you do in production: validate at I/O boundaries.


Example: Money type (avoid mixing currencies)

type Currency = "INR" | "USD";
 
type Money<C extends Currency> = Brand<
  { amount: number; currency: C },
  `Money:${C}`
>;
 
function money<C extends Currency>(amount: number, currency: C): Money<C> {
  return { amount, currency } as Money<C>;
}
 
function add<C extends Currency>(a: Money<C>, b: Money<C>): Money<C> {
  return money(a.amount + b.amount, a.currency);
}
 
const a = money(100, "USD");
const b = money(200, "USD");
add(a, b);
 
// add(a, money(50, "INR")) // ❌ type error

Production rule of thumb

  • Use brands for values that have domain meaning (IDs, emails, URLs, money).
  • Don’t brand everything; brand the “high-cost mistakes”.
  • Create branded values through parsers/validators at boundaries, not random casts.

Interview questions

  1. Q: Why do branded types work in TS?

    • A: The phantom property makes the type structurally different while having zero runtime cost.
  2. Q: Where should validation happen?

    • A: At I/O boundaries (API responses, forms, DB reads), so internal code stays safe.

Quick recap

  • TS is structural; brands add nominal-ish safety.
  • Brands prevent mixing different string meanings.
  • Validate once at boundaries, then pass branded values internally.
Companion Book

Looking for structured, offline interview prep?

Free online lessons • Portable Amazon Kindle / Paperback

Frontend Interview Guide (2025 Edition) cover
Recommended for React & Frontend Rounds

Frontend Interview Guide (2025 Edition)

By Rajesh Dhiman

Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.

View on Amazon ↗Kindle & Paperback
JavaScript Interview Guide cover
Recommended for Core JS Rounds

JavaScript Interview Guide

By Rajesh Dhiman

Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.

View on Amazon ↗Kindle & Paperback

Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.

RD

Written by Rajesh Dhiman

Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.