Lesson 26 of 80
TypeScript compares types by shape, not by name — branding is the trick that fakes name-based (nominal) typing for the specific cases where two structurally identical types genuinely shouldn't be interchangeable.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 2
Does the `__brand` field in a branded type like `string & { readonly __brand: "UserId" }` exist in the compiled JavaScript at runtime?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
From the structural typing lesson: TypeScript considers two types compatible if they have the same shape, regardless of name. Usually that's exactly what you want — but sometimes it's actively wrong:
type UserId = string;
type PostId = string;
function getUser(id: UserId) { /* ... */ }
const postId: PostId = "post_123";
getUser(postId); // ✅ compiles — but this is a real bug, wrong id entirely
UserId and PostId are both just string underneath — structurally identical, so TypeScript happily lets you pass one where the other is expected. Nothing about the shape distinguishes them, even though they mean completely different things.
type UserId = string & { readonly __brand: "UserId" };
type PostId = string & { readonly __brand: "PostId" };
function makeUserId(id: string): UserId { return id as UserId; }
function getUser(id: UserId) { /* ... */ }
getUser(postId); // ❌ now a real compile error
getUser(makeUserId("u_1")); // ✅
The __brand field doesn't exist at runtime at all — it's a type-space-only marker (the type-vs-value-space idea) that makes UserId and PostId structurally incompatible even though both are still, underneath, plain strings. This opts a specific pair of types out of structural typing on purpose.
type UserId = string; type OrderId = string; — structurally identical, TypeScript can't tell them apart.
as UserId performs no actual transformation — a branded type is purely a compile-time fiction layered on top of a real, ordinary string.SafeHtml branded type, distinct from plain string) are the classic cases; branding everything adds friction with no payoff.makeUserId, or a parseEmail(s: string): Email | null that validates first) is the idiomatic way to produce a branded value — you can't just assert any string as branded and expect the validation to have actually happened."How would you prevent a UserId and a PostId — both just strings underneath — from being accidentally swapped?" is a great, concrete question, and branded types is the real, idiomatic TypeScript answer that most candidates haven't encountered unless they've hit this exact bug in production.