TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
98: Using Symbols with TypeScript
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 symbolcalledPluginDataKey. - Define an interface
AppConfigthat has a standard string propertyenvand uses thePluginDataKeyto store aMapwhere 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 thePluginDataKey, but the settings are still accessible via your getter function.
There are no comments for now.