Menu

JavaScript Private Fields: The # Syntax for Class Privacy

How the # prefix makes class fields and methods truly private in JavaScript — syntax, rules, and why the underscore convention isn't enough.

The Problem With Underscores

For years, JavaScript didn't have private fields. The convention was to prefix a property with an underscore and hope everyone respected it:

index.js
Output
Click Run to see the output here.

_count looks private but isn't. Any caller can read it, write it, or delete it. The underscore is a polite sign on the door; the door itself is wide open.

Modern JavaScript fixed this with real private fields, marked with a #.

The # Makes It Private

Prefix the field name with # in the declaration and at every access site:

index.js
Output
Click Run to see the output here.

From inside the class, this.#count works normally. From outside, it doesn't exist:

index.js
Output
Click Run to see the output here.

The # is literally part of the field's name. It's not a modifier keyword like private in other languages — it's a sigil the parser uses to look up a separate, protected slot on the object. That's why the error shows up at parse time, before the code even runs.

Private Methods and Getters

Fields aren't the only thing you can mark private. Methods, getters, and setters all accept the # prefix:

index.js
Output
Click Run to see the output here.

#assertPositive is an internal helper. It's not part of the public API, so making it truly private means nobody accidentally calls it from outside — and nobody can depend on it, which keeps you free to rename or remove it later.

Private Static Members

Static members can be private too. Prefix them with # the same way:

index.js
Output
Click Run to see the output here.

Private statics live on the class itself, not instances. Useful for counters, caches, or configuration that shouldn't leak out of the class.

Subclasses Can't See Them

This trips people up coming from Java or C#. JavaScript's private fields are class-private, not instance-private. A subclass can't reach into its parent's private fields:

index.js
Output
Click Run to see the output here.

There's no protected in JavaScript. If a subclass needs the data, the parent must expose a method, getter, or (less commonly) a non-private field. The design is deliberate: private really means private, and inheritance doesn't poke holes in it.

Checking for a Private Field With in

Sometimes you want to confirm an object actually belongs to your class — a brand check. The in operator works with private field names inside the class:

index.js
Output
Click Run to see the output here.

Because only Wallet can create objects with #balance, #balance in obj is a reliable test that obj is a genuine Wallet instance. Faster and safer than instanceof in some edge cases, since private fields can't be forged from outside.

A Common Gotcha: Plain Objects Don't Have It

Private fields live on instances created by the class constructor. If you try to use one on an object that wasn't created by new, it throws:

index.js
Output
Click Run to see the output here.

Calling the method with a non-Point this throws at runtime. This is the mechanism behind the brand check above — private fields are tied to the specific class that declared them, not to any object that happens to look right.

When to Reach for #

Default to private fields whenever a piece of state or a helper isn't part of the class's public API. The reasons:

  • Refactoring freedom. Callers can't depend on internals they literally can't see.
  • Real encapsulation. No accidental reads, writes, or deletions from outside code.
  • Cleaner autocomplete. Editors don't suggest private members to external callers.

Use public properties when something is genuinely part of the interface. Use a getter (get name()) when you want read-only access to a private field. Skip the underscore convention — it was a workaround for a gap the language has now filled.

index.js
Output
Click Run to see the output here.

#celsius is the hidden storage; celsius and fahrenheit are read-only views. The caller can't corrupt the internal state, and the class is free to change how it stores the value later.

Next: Prototypes

Classes are mostly syntactic sugar over JavaScript's prototype system — the older, more fundamental model the language is actually built on. Understanding prototypes explains why this behaves the way it does, how inheritance really works, and what a class extends under the hood. That's the next page.

Frequently Asked Questions

How do you declare a private field in JavaScript?

Prefix the field name with # — both in the declaration and every time you access it. class Counter { #count = 0; increment() { this.#count++; } } creates a truly private field. The # is part of the name, not an operator.

What's the difference between #field and _field in JavaScript?

_field is just a naming convention — the property is still public and anyone can read or write it. #field is enforced by the language: code outside the class literally cannot access it, and trying throws a SyntaxError at parse time. Use # when you want real privacy.

Can subclasses access private fields from a parent class?

No. Private fields are scoped to the class that declares them — not even subclasses can touch them. If a subclass needs access, the parent class has to expose a method or getter. This is stricter than protected in other languages, and it's intentional.

Can I check if an object has a private field?

Yes, with the in operator inside the class: #field in obj returns true if obj has that private field. This is useful for brand checks — confirming an object is actually an instance of your class before calling methods on it.

Learn to code with Coddy

GET STARTED