TypeScript Access Modifiers in 2026: Why `private` Fields Beat `#` and When the Opposite Is True
TypeScript Access Modifiers in 2026: Why private Fields Beat # and When the Opposite Is True This article was written with the assistance of AI, under human supervision and review. Most privacy bugs in TypeScript codebases stem from misunderstanding the two fundamentally incompatible encapsulation models: compile-time private modifiers and runtime ECMAScript # fields. Teams pick one arbitrarily,…
This piece delves into the differences between TypeScript's `private` fields and ECMAScript `#` fields for 2026, explaining when each should be used.
The `private` modifier exists only in the TypeScript type system. During development, the compiler blocks access to these fields, but the resulting JavaScript contains regular properties. This makes `private` a documentation tool rather than a security feature.
Consider the `UserSession` class example. TypeScript flags an attempt to access the `token` property, but the generated JavaScript allows anyone to read or modify it. This has three implications:
1. Publishing libraries on npm exposes internal fields to consumers.
2. Dynamically loading third-party modules can access private fields.
3. Reflection-based frameworks like serializers or ORMs can inspect private properties.
`private` fields are simple to use, with IntelliSense hiding them from developers and refactoring tools understanding the visibility contract. However, they fail when the runtime environment doesn't enforce the privacy contract. For instance, if an internal dashboard assumes TypeScript usage, a Python service importing the JavaScript bundle can directly mutate session tokens.
In contrast, ECMAScript `#` fields enforce runtime privacy through WeakMap storage. These fields remain invisible to reflection and external code, making them truly inaccessible. TypeScript preserves the `#` syntax when targeting ES2022 or later.
Consider the `SecureWallet` example. The `#balance` and `#encryptionKey` fields are inaccessible to reflection and external code. However, migrating from `private` to `#` changes the public API surface and breaks reflection-based tooling. Codebases must establish explicit conventions to avoid mixing both patterns inconsistently.
In summary, the choice between `private` and `#` depends on trust boundaries: controlling every consumer or running code in hostile environments. In 2026, TypeScript 5.7+ supports both natively, but understanding the limitations of each pattern is crucial to prevent silent failures in production.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.