Skip to Content
Course content

98: Using Symbols with TypeScript

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've noticed a recurring pattern when developers first encounter Symbols in TypeScript: they treat them as a "hack" for creating private properties. I'll see code where someone uses a Symbol to hide a piece of internal state on an object, believing that because the property doesn't show up in a for...in loop or Object.keys(), it's effectively invisible and inaccessible to the rest of the application.

Symbols aren't private, they're just unique identifiers

Let me show you why that line of thinking is dangerous. If you believe a Symbol makes a property private, you're relying on "security by obscurity" rather than actual language constraints. Symbols are designed to avoid naming collisions—not to hide data from a determined developer.

const INTERNAL_ID = Symbol('id');

interface User {
  name: string;
  [INTERNAL_ID]: number;
}

const user: User = {
  name: 'Alice',
  [INTERNAL_ID]: 12345
};

console.log(Object.keys(user)); // ['name'] - Looks private!
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)] - Oops.

As you can see, Object.getOwnPropertySymbols() exposes everything. If you actually need private members, use the #private syntax introduced in modern JavaScript/TypeScript. Symbols are for when you want to add a property to an object that is guaranteed not to clash with any other property, even if another library adds a property with the exact same string name.

Using unique symbol for precise type checking

Now, here is where TypeScript makes things interesting. If you just use the type symbol, you're telling TypeScript "this is some symbol, I don't care which one." But usually, you care exactly which symbol you're using because that symbol acts as the specific key to your data.

If you use const MY_KEY: unique symbol = Symbol('key'), TypeScript treats that specific symbol as its own unique type. This allows the compiler to ensure that you are accessing the property with the exact constant you defined, rather than just any random symbol.

// This is a general symbol type
const genericSymbol: symbol = Symbol('generic');

// This is a unique symbol type
const metadataKey: unique symbol = Symbol('metadata');

interface Document {
  title: string;
  [metadataKey]: { author: string; version: number };
}

const doc: Document = {
  title: 'TypeScript Guide',
  [metadataKey]: { author: 'Mentor', version: 1 }
};

// TypeScript knows exactly what type is returned here
const author = doc[metadataKey].author; 

// This would fail if we tried to use a generic symbol to index the object
// because 'symbol' is too broad for the specific 'unique symbol' key.

I personally use this pattern when writing plugins or middleware. If I'm adding data to a request object that passes through ten different functions, I don't want to risk naming my property "id" or "user" and accidentally overwriting someone else's data. Using a unique symbol creates a dedicated "slot" on that object that belongs exclusively to my logic.




📋 Practical Task

Implementing a Collision-Proof Plugin Metadata Store

You are building a plugin system where multiple third-party plugins need to attach their own configuration data to a shared AppConfig object. To prevent plugins from overwriting each other's data, you must use Symbols.

Your Task:

  • Create a unique symbol called PluginDataKey.
  • Define an interface AppConfig that has a standard string property env and uses the PluginDataKey to store a Map where the key is the plugin name (string) and the value is the plugin's specific settings (an object).
  • Implement a function setPluginSetting(config, pluginName, settings) that adds settings to the map stored under the symbol.
  • Implement a function getPluginSetting(config, pluginName) that retrieves those settings.
  • Demonstrate that Object.keys(config) does not list the PluginDataKey, but the settings are still accessible via your getter function.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.