symbol
Types unique property keys created by Symbol(), with unique symbol for a single fixed identity.
let sym: symbol = Symbol("key");Errors
Annotating a non-const binding `unique symbol` is an error: "A variable whose type is a 'unique symbol' type must be 'const'."
Changelog
- PHP 2.7unique symbol added — a subtype of symbol allowed only on const declarations and readonly static properties, referenced via typeof. Const-named properties (computed keys from symbol constants) also landed here.
- PHP 2.7No two unique symbol types are assignable or comparable to each other, so comparing two of them is a compile error.
symbol is a JavaScript primitive whose values are guaranteed unique. Symbol("key") and Symbol("key") produce two different values; the string is only a description for debugging. The type symbol covers all of them.
symbol versus unique symbol
unique symbol is a subtype that stands for one specific symbol, tied to the declaration that produced it. It is only allowed on const declarations and readonly static properties, and to name that type elsewhere you write typeof THE_CONST. Because each has a separate identity, no two unique symbol types are assignable or comparable to each other — comparing two of them is a compile error, not a false result.
This is what makes a symbol usable as a type-level key. With a plain symbol-typed constant, obj[key] is not statically known; with a unique symbol, TypeScript tracks the property exactly.
Why reach for it
Symbol keys cannot collide with string keys, are skipped by Object.keys, for…in and JSON.stringify, and are only reachable via Object.getOwnPropertySymbols. That makes them the right tool for metadata attached to someone else's object, and for protocol hooks like Symbol.iterator.
Example
When to use it
- Attaching library metadata to a user's object without risking a name collision
- Implementing Symbol.iterator or Symbol.asyncIterator on a custom collection
- Defining a protocol method key in a shared module so implementors reference the same constant
Frequently asked questions
What is the difference between symbol and unique symbol in TypeScript?
`symbol` is the type of all symbols; `unique symbol` denotes one particular symbol tied to its declaration. `unique symbol` is only permitted on `const` declarations and `readonly static` properties, and you refer to it elsewhere as `typeof THE_CONST`. Only a `unique symbol` can act as a statically known property key, so `obj[KEY]` gets a real type.
Why does Object.keys not show my symbol property?
Symbol-keyed properties are deliberately excluded from `Object.keys`, `for…in`, `Object.entries` and `JSON.stringify`. Retrieve them with `Object.getOwnPropertySymbols(obj)` or `Reflect.ownKeys(obj)`. Object spread and `Object.assign` do copy enumerable own symbol properties, so behaviour differs between serialisation and cloning.
Why can't I compare two symbol constants in TypeScript?
If both are inferred as `unique symbol`, TypeScript knows their identities are distinct and reports the comparison as having no overlap. That is a correctness warning: the check would always be false. Annotate one as `symbol` if you genuinely want a runtime comparison against an arbitrary symbol.
More examples
A symbol key as a collision-proof metadata slot
Prints `a plain string key`, `import`, `[ 'id', 'meta' ]`, `{"id":1,"meta":"a plain string key"}`, then `import`. The symbol key coexists with a string key literally named `meta` without conflict, stays out of `Object.keys` and JSON, but is carried across by object spread — the three behaviours that decide whether symbol metadata is right for your case.
const META: unique symbol = Symbol("meta");
interface Tagged { [META]?: { source: string } }
function tag<T extends object>(value: T & Tagged, source: string): T & Tagged {
value[META] = { source };
return value;
}
const record = tag({ id: 1, meta: "a plain string key" }, "import");
console.log(record.meta); // the string key is untouched
console.log(record[META]?.source); // the symbol slot
console.log(Object.keys(record)); // ['id', 'meta']
console.log(JSON.stringify(record)); // symbol slot omitted
console.log({ ...record }[META]?.source); // spread does copy own symbolsImplementing Symbol.iterator
Prints `[ 1, 2, 3, 4 ]`, `6`, then `0`, `1`, `2`. `Symbol.iterator` is a well-known symbol whose type is `unique symbol`, so `[Symbol.iterator]()` declares exactly the member `Iterable<number>` requires — a string key named `"Symbol.iterator"` would not satisfy the interface.
class Range implements Iterable<number> {
constructor(private from: number, private to: number) {}
[Symbol.iterator](): Iterator<number> {
let current = this.from;
const end = this.to;
return {
next(): IteratorResult<number> {
return current < end
? { value: current++, done: false }
: { value: undefined, done: true };
},
};
}
}
console.log([...new Range(1, 5)]);
console.log(Math.max(...new Range(3, 7)));
for (const n of new Range(0, 3)) console.log(n);
Discussion