All topics
Objectsintermediate

Private Class Fields and Encapsulation

The # syntax for true private fields and methods in JavaScript classes, enforced by the engine rather than by convention.

Private class fields, using the # prefix, were added to give JavaScript classes genuine, engine-enforced privacy for the first time — before this, 'private' fields were only a naming convention (a leading underscore) that provided no actual protection, since any code could still access obj._privateField directly. Interviewers ask about this specifically to check awareness of a relatively recent but now widely-supported language feature.

Public class fields are like an office with a glass door anyone can walk through and rearrange the furniture in; private fields are like a locked back office that only employees hired specifically for that office (methods of that exact class) have a key to, and even attempting to peek in from outside gets stopped at the door before you even get close.

Key Concepts

1
A private field is declared inside a class body with a # prefix (#balance = 0;), and it can only be accessed or assigned from within that exact class's own methods — not from subclasses, not from outside code, and not even via reflection tools like Object.keys() or for...in, which simply don't see private fields at all, since they aren't ordinary properties in the usual sense. Attempting to access instance.#balance from outside the class throws a SyntaxError at parse time (not even a runtime error — the engine catches it before the code even runs), which is a much stronger guarantee than the old underscore convention ever provided.
##balance = 0;Object.keys()for...ininstance.#balance
2
Private methods work the same way (#calculateInterest() {}), letting you hide internal implementation helpers that consumers of the class should never call directly, keeping the public API surface intentionally small and stable. There's also a special syntax, #field in obj, for safely checking whether an object actually has a given private field without throwing, useful in scenarios involving multiple classes or checking an object's type in duck-typing-adjacent code.
#calculateInterest() {}#field in obj
3
A notable limitation: private fields are tied to the exact class they're declared in, not inherited the way public properties conceptually flow through the prototype chain — a subclass cannot directly access a private field declared in its parent class, even though it can access public/protected-by-convention members. This is a deliberate design choice reinforcing that private state is truly private to its declaring class, encouraging you to expose a protected public (or convention-based) API for anything a subclass genuinely needs to reach into.