Lesson 45 of 80
Every Symbol() call produces a genuinely distinct runtime value — unique symbol is the type system's way of tracking that one specific value, not just 'some symbol'.
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.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →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
The branded-types lesson fakes a distinct identity for two structurally-identical strings by tagging one with a string-literal field, like { __brand: "UserId" }. It works, but the "brand" is just a label — two unrelated files could both write __brand: "UserId" and TypeScript would treat their types as the same, purely by coincidence of spelling.
A unique symbol is a stronger version of the same instinct, backed by something real: every call to Symbol() produces a genuinely distinct runtime value, forever, even if you call it with the exact same description string twice. unique symbol is the type that tracks "specifically this one symbol," not "a symbol, generically."
const a = Symbol("id");
const b = Symbol("id");
a === b; // false — always, no matter how many times you run this
[Symbol.iterator] as a keyclass Range implements Iterable<number> {
constructor(private start: number, private end: number) {}
[Symbol.iterator](): Iterator<number> {
let current = this.start;
const end = this.end;
return {
next(): IteratorResult<number> {
return current < end
? { value: current++, done: false }
: { value: undefined, done: true };
},
};
}
}
for (const n of new Range(1, 4)) console.log(n); // 1 2 3
Symbol.iterator is declared unique symbol in TypeScript's own standard library types — not the general symbol type. That specificity is why Iterable<T>'s interface can require a computed property [Symbol.iterator] at all: TypeScript needs to know exactly which symbol a computed key refers to, at the type level, to match it up across different declarations. A value merely typed symbol could be any of infinitely many runtime symbols, so it can't be used as a specific, checkable key — only a unique symbol carries enough identity for that.
const brand = Symbol("UserId"); // type: typeof brand — a unique symbol
type UserId = string & { [brand]: true };
const s: symbol = Symbol("UserId"); // type: symbol — deliberately widened
type Fake = string & { [s]: true }; // ❌ a computed property key must be of type unique symbol
unique symbol doesn't stop an explicit as lie — like every TypeScript assertion, nothing here is runtime-enforced. What it actually buys you is collision-safety: no other code, not even someone typing the identical source Symbol("UserId") in a different file, can accidentally produce a matching key, because every Symbol() call is a new, unequal value.Symbol.for(key) deliberately breaks that guarantee — it returns the same symbol for the same string key across your whole program (a global registry), which is the opposite of "one-of-a-kind." That's exactly why a value from Symbol.for is never typed unique symbol.const declarations (or their class-field equivalent) qualify. A let symbolVar = Symbol("x") is immediately widened to plain symbol, even though the runtime value is just as unique as a const one — the type-level guarantee specifically requires that the binding can never be reassigned to a different symbol.Symbol.iterator, Symbol.asyncIterator, and friends are the common case; hand-writing your own unique symbol brand is rarer and usually only worth it when string-literal branding's coincidental-collision risk is a genuine concern."Why is Symbol.iterator typed as unique symbol instead of plain symbol?" is an unusual but precise question — a strong answer connects it back to why TypeScript needs a specific, known identity to type-check a computed property key at all, not just "symbols are unique at runtime" as a vague fact.