In the rapidly evolving landscape of web development, TypeScript has established itself as an indispensable tool for building scalable and maintainable applications. By adding a robust type system to JavaScript, it allows developers to catch errors early and document their code more effectively. However, as the ECMAScript standard—the foundation upon which JavaScript and TypeScript are built—continues to progress, new syntactical features are introduced that can sometimes cause confusion. One such feature is the “hashtag” or pound symbol (#).
To the uninitiated, seeing a hashtag prepended to a variable name within a TypeScript class might look like a typo or a specialized decorator. In reality, the hashtag represents a significant shift in how encapsulation is handled in the JavaScript ecosystem. This article explores the technical nuances of the hashtag in TypeScript, specifically focusing on “Private Class Fields,” how they differ from the traditional private keyword, and why they are essential for modern software architecture.

The Evolution of Privacy in TypeScript
Before we can appreciate the role of the hashtag, we must understand the historical context of data hiding in JavaScript. Traditionally, JavaScript lacked a native way to make class members truly private. Developers relied on naming conventions, such as prefixing a variable with an underscore (_secret), to signal to other developers that a property should not be accessed directly. However, this was merely a “gentleman’s agreement”; the property remained fully accessible to any external script.
From the private Keyword to the # Symbol
When TypeScript arrived, it introduced the private access modifier. This was a revolutionary addition for developers coming from languages like C# or Java. It allowed developers to mark a property as private, and the TypeScript compiler would throw an error if someone tried to access it outside the class.
While the private keyword provided compile-time safety, it had a major limitation: it vanished once the code was transpiled to JavaScript. At runtime, a “private” TypeScript variable was just a standard public property. The introduction of the hashtag (#) in the ECMAScript 2020 (ES11) specification changed this. TypeScript adopted this syntax to support “Hard Privacy,” ensuring that private members are inaccessible not just at the type-checking stage, but also at runtime.
The Role of TC39 and ECMAScript Standards
The transition to the hashtag syntax was not a decision made by the TypeScript team in isolation. It was the result of years of deliberation by the TC39 committee (the body that governs JavaScript standards). The goal was to provide a mechanism for encapsulation that was baked into the language engine itself. By supporting the # syntax, TypeScript aligns itself with modern JavaScript standards, ensuring that code written today remains compatible with the future of the web platform.
Technical Implementation: How to Use Private Identifiers
The hashtag in TypeScript denotes a “Private Identifier.” When you prefix a class member—be it a property, a method, or a getter/setter—with #, you are declaring that this member is strictly local to the containing class.
Syntax Rules for the Hashtag Prefix
The syntax for private fields is rigid and follows specific rules designed to prevent ambiguity. Unlike the private keyword, which is an modifier placed before the variable name, the hashtag is actually part of the variable name itself.
class SecureVault {
#encryptionKey: string;
constructor(key: string) {
this.#encryptionKey = key;
}
#validateKey() {
return this.#encryptionKey.length > 16;
}
}
In the example above, #encryptionKey is the full name of the property. You cannot refer to it as this.encryptionKey. This distinction is vital because it allows the JavaScript engine to perform optimizations and ensures that the private field does not collide with any public properties of the same name.
Initializing and Accessing Private Members
Private fields must be declared before they are used. You cannot dynamically add a private field to an object at runtime; they must be statically defined within the class body. This constraint is a departure from standard JavaScript objects, which are traditionally very fluid. By requiring static declaration, TypeScript and JavaScript engines can guarantee that every instance of the class has the exact same “shape,” leading to better performance in the long run.
Hashtag (#) vs. Private Keyword: The Critical Differences

The most common question developers ask is: “If I already have the private keyword, why do I need the hashtag?” The answer lies in the distinction between “Soft Privacy” and “Hard Privacy.”
Hard Privacy vs. Soft Privacy
The private keyword in TypeScript offers “Soft Privacy.” It is a tool for the developer and the compiler. Once the code is compiled to JavaScript, the private keyword is stripped away, and the property becomes a regular public property. If a developer uses a bypass like (myObject as any).privateProperty, they can still access the data.
The hashtag (#), conversely, offers “Hard Privacy.” It is enforced by the JavaScript runtime (Node.js or the browser). Even if you cast the object to any, the runtime will throw a SyntaxError or an Accessor error if you try to reach into the object to grab a private field. This makes the hashtag the preferred choice for security-sensitive data or for library authors who want to strictly control their internal API.
Runtime Behavior and Encapsulation
Another critical difference is how these fields appear during inspection. If you use console.log() on an object with a private property, that property will appear in the output. If you use a private field with #, many modern environments will either hide the field entirely or mark it in a way that indicates it cannot be touched.
Furthermore, private fields are not subject to standard JavaScript property enumeration. They won’t show up in Object.keys(), and they cannot be intercepted by JavaScript Proxy objects. This level of isolation is impossible to achieve with the traditional private keyword.
Performance Considerations
Because private fields are handled by the engine’s internal optimization paths, they can occasionally be faster than using WeakMaps (the old workaround for private data). However, developers should be aware that because they are “hard” private, they require a bit more overhead in terms of memory management within the engine to ensure that the privacy boundaries are never breached.
Use Cases and Best Practices for Modern TypeScript Development
Deciding between private and # often depends on the specific needs of your project. However, as the ecosystem moves forward, the hashtag is becoming the standard for new development.
Avoiding Property Name Collisions in Subclasses
One of the most powerful features of the hashtag is that it prevents name collisions during inheritance. In standard JavaScript/TypeScript, if a base class has a private property named data and a subclass also defines a property named data, they can conflict or overwrite each other in unexpected ways at runtime.
With private identifiers, the #data field in the base class is completely unique to the base class. Even if the subclass defines its own #data, the two exist in separate “slots” within the object. This allows for much safer class hierarchies, especially in large-scale applications where different teams might be working on different parts of a class tree.
When to Choose the Hashtag Over the Keyword
A good rule of thumb is to use the # hashtag when:
- Library Development: You are building a library and want to ensure that users cannot access internal state, preventing them from relying on “hidden” features that might change in future versions.
- Security: You are handling sensitive information like tokens, keys, or raw data that should never be exposed to the rest of the application.
- Runtime Integrity: You need to guarantee that no external logic (including third-party scripts) can modify the internal state of your objects.
Use the private keyword when:
- Legacy Codebases: You are working in a project that hasn’t yet migrated to modern ECMAScript targets.
- Lightweight Constraints: You only need type-checking protection and don’t care about runtime access.
- Simplicity: You prefer the cleaner look of the keyword and aren’t worried about the “hard” privacy guarantees.
Compatibility, Tooling, and the Future of TypeScript Encapsulation
As with any newer feature, compatibility is a key consideration for tech professionals. The hashtag syntax requires a modern environment.
Transpilation and Downleveling
TypeScript is capable of “downleveling” private fields for older environments (like IE11), but it does so by using WeakMaps. While this emulates the behavior of private fields, it can significantly increase the size of the compiled code and introduce a slight performance penalty. If your project targets ES2020 or higher, TypeScript will emit the hashtag syntax directly, allowing the browser to handle it natively.

Current Browser and Environment Support
Today, all major evergreen browsers (Chrome, Firefox, Safari, Edge) and Node.js (since version 12) fully support private class fields. For the vast majority of modern web applications, the hashtag is ready for production use.
As the TypeScript community continues to embrace the hashtag, we are seeing a shift in coding standards. The hashtag isn’t just a shorthand; it’s a declaration of intent. It signals that a piece of data is truly sovereign to the class it lives in. By mastering this syntax, TypeScript developers can write more resilient, secure, and future-proof code, fully leveraging the power of the modern JavaScript engine. Whether you are building a complex AI-driven tool or a simple utility app, understanding the hashtag is an essential step in your journey toward technical excellence.
aViewFromTheCave is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to Amazon.com. Amazon, the Amazon logo, AmazonSupply, and the AmazonSupply logo are trademarks of Amazon.com, Inc. or its affiliates. As an Amazon Associate we earn affiliate commissions from qualifying purchases.