private Fields vs #private: Which One Fits
Run the same class with the private keyword and with a hash field, and compare the output, the errors and what outside code can still reach. After this, you can pick one and say why.
TypeScript gives you two ways to make a class field private: the private keyword and JavaScript's own # prefix. They look similar and behave differently, and the difference only shows up when the code runs. This page puts the same class through both and ends with a rule for choosing. The access modifiers are explained in the course lesson on classes.
Step 1: the same class, written twice
class WithKeyword {
private balance: number;
constructor(balance: number) {
this.balance = balance;
}
show(): number {
return this.balance;
}
}
class WithHash {
#balance: number;
constructor(balance: number) {
this.#balance = balance;
}
show(): number {
return this.#balance;
}
}
const a = new WithKeyword(500);
const b = new WithHash(500);
console.log(a.show(), b.show());
console.log(a);
console.log(b);
console.log(JSON.stringify(a), JSON.stringify(b));
500 500
WithKeyword { balance: 500 }
WithHash {}
{"balance":500} {}
Both classes answer show() the same way. The difference is in what the outside can see. The keyword version prints its balance and puts it in the JSON. The hash version prints an empty object and has nothing to serialize. Node does not show # fields when it prints an object, but they are still stored on it.
Step 2: what the compiler says
class WithKeyword {
private balance = 500;
}
class WithHash {
#balance = 500;
}
const a = new WithKeyword();
const b = new WithHash();
console.log(a.balance);
console.log(b.#balance);
access.ts(12,15): error TS2341: Property 'balance' is private and only accessible within class 'WithKeyword'.
access.ts(13,15): error TS18013: Property '#balance' is not accessible outside class 'WithHash' because it has a private identifier.
Both are compile errors, with different messages. So at compile time, the two behave alike.
Step 3: what the outside can still do
class WithKeyword {
private balance = 500;
}
class WithHash {
#balance = 500;
}
const a = new WithKeyword();
const b = new WithHash();
console.log(a["balance"]);
console.log((b as any)["balance"]);
console.log((b as any)["#balance"]);
500
undefined
undefined
Bracket notation gets past private, because TypeScript allows it as an escape hatch, and the field is a normal property. It does not get past #. The hash field is not a property with a name that code can look up. Only code written inside the class body can reach it.
Step 4: choose
| Use | When |
|---|---|
private | You want a team rule that the compiler enforces, and you may need tests or debugging tools to look inside. |
#field | The data must stay inside the class at run time, including from plain JavaScript and from any. |
Two more points. With the default settings of TypeScript 7.0.2, the compiled file keeps #balance as plain JavaScript, so Node and current browsers run it natively. And a class can mix both, but a codebase should not: pick one and say so in the style guide.
⚠ Warning: Neither one is a security feature for code that ships to a browser. A user can open the developer tools and read whatever your code can read. Keep real secrets on the server.
Common mistakes
- Choosing
privateand believing it hides the field fromconsole.log,JSON.stringifyorObject.keys. - Using
#and then trying to read the field from a subclass. A subclass cannot, so useprotectedfor fields that subclasses need. - Mixing both styles in one file for no reason.
Related course: TypeScript Intermediate.
About
Run the same class with the private keyword and with a hash field, and compare the output, the errors and what outside code can still reach. After this, you can pick one and say why.
- Responsible
- Jay P
- Last Update
- 10/16/2026
- Reading Time
- 3 min
Reviews
Sign in to write a reviewComments (0)
Sign in to commentNo comments yet.