ProgrammingBackend Developer

How does the symbol typing mechanism (Symbol) work in TypeScript? What are the advantages and features of using Symbol as object keys, and what nuances should be considered when designing APIs in TypeScript?

Pass interviews with Hintsage AI assistant

Answer.

Background:

The Symbol type was introduced in JavaScript (ES6) to create unique identifiers that are guaranteed not to collide with other object properties. TypeScript has supported symbols since the inclusion of ES6 compatibility.

Problem:

Before Symbol, strings were commonly used as keys for object properties. This led to errors when extending or reusing objects: random name collisions and the inability to hide private properties (even via convention). Symbol allowed for the creation of unique, externally invisible keys, but questions about typing arose — how to describe types with Symbol keys and use them safely in APIs?

Solution:

TypeScript supports symbols as values and types; however, the typing of Symbol keys has its peculiarities. To create a symbol, you can use the global constructor or the global symbol registry. In interfaces or types, keys with symbols must explicitly indicate the type as symbol, and access to such properties is only done with a saved reference to the Symbol.

Code example:

const SECRET = Symbol('secret'); interface SecretObject { [SECRET]: string; visible: string; } const obj: SecretObject = { visible: 'see me', [SECRET]: 'hidden', }; console.log(obj.visible); // 'see me' // console.log(obj["secret"]); // Error: property does not exist! console.log(obj[SECRET]); // 'hidden'

Key features:

  • Symbol guarantees the uniqueness of the property key, which is impossible for any string key.
  • Properties with Symbol keys do not appear in normal enumeration (for...in, Object.keys). This is convenient for hidden properties.
  • Not all standard operations (e.g., JSON.stringify) account for Symbol keys — this is important for serialization and deserialization.

Tricky questions.

Can a Symbol be automatically converted to a string when used in objects?

No, a Symbol cannot be automatically converted to a string; an attempt to do so (e.g., via concatenation) will result in an error.

const mySymbol = Symbol('desc'); // alert('prefix_' + mySymbol); // TypeError

Can Symbol keys be enumerated via Object.keys?

No, Object.keys and for...in ignore Symbol keys. To retrieve such keys, Object.getOwnPropertySymbols is used.

const sym = Symbol('a'); const obj = { [sym]: 42 }; Object.keys(obj); // [] Object.getOwnPropertySymbols(obj); // [Symbol(a)]

Are properties with Symbol keys passed when copying via Object.assign?

Yes, Object.assign copies both string and symbol keys, unlike JSON.stringify.

const s = Symbol('s'); const o1 = { [s]: 123, foo: 'bar' }; const o2 = Object.assign({}, o1); o2[s]; // 123

Typing errors and anti-patterns

  • Creating symbols directly in multiple places (implicitly reusing Symbol(…)). This leads to different, non-matching keys.
  • Treating Symbol keys like regular strings — this leads to access errors and loss of properties.
  • Expecting serialization of Symbol keys via JSON.stringify — these properties are lost.

Real-life example

Negative case

A developer used string keys ('_private') for private properties, relying on convention. In Team B, a similar string was accidentally added — the properties collided, resulting in an unpredictable error.

Pros:

  • Quick prototyping.

Cons:

  • No real privacy.
  • Possibility of name collisions.
  • Data leakage between parts of the system.

Positive case

A second developer used Symbol for hidden properties (e.g., Symbol('internal')). Now even within the team, internal data cannot be accidentally overridden: access is only possible if there is a reference to the specific Symbol.

Pros:

  • Reliable privacy.
  • Minimal risk of collisions.

Cons:

  • Not an obvious interface for new employees.
  • Debugging hidden fields is harder.