cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Private fields & methods

Learn JavaScript private fields, private methods, private static members, brand checks, and when to choose class privacy instead of closures or WeakMaps.

By the end, you can
  • 01
    Lock class stateUse #private fields, methods, and getters for real language-enforced encapsulation.
  • 02
    Check brands safelyUse #x in obj to ask whether an object was built by a class.
  • 03
    Choose a privacy patternCompare private fields with closures and WeakMap privacy.

Real privacy for class objects

JavaScript objects are usually open. If an object has a property named balance, other code can read it, write it, delete it, or accidentally overwrite it. Naming it _balance is only a convention: a polite sign that says “please don’t touch.”

A private class member starts with #, such as #balance or #validate(). The # is not part of a string property name. It creates a private name that only code inside the declaring class body can use. Outside code cannot even parse account.#balance.

Real-life analogyA private field is a safe inside the object

Imagine every account object carries a safe. The public methods—deposit, withdraw, and balance label—know the combination. Other code can hold the object, pass it around, and ask it to do work, but it cannot open the safe directly.

In real life: The safe inside the bank account folder
In JavaScript: The private field #balance on the object
In real life: Staff who know the combination
In JavaScript: Methods declared inside the class
In real life: A sticky note reading “do not touch”
In JavaScript: An _balance naming convention
In real life: Asking whether this folder came from the bank
In JavaScript: A brand check: #balance in obj

Where the analogy stops: A real safe can be drilled open. JavaScript private names are enforced by the parser and runtime, but debugging tools may still display them to the developer who owns the process. Privacy is for program correctness, not secret storage against the machine owner.

The one sentence definition

Private members are class fields, methods, accessors, or static members whose names start with # and are usable only inside the class body that declares them.

This lesson connects the privacy patterns you met in Closures, Closure patterns, and WeakMap & WeakSet to the class syntax from Class basics.

#private fields & methods

INTERACTIVE

Private instance fields are declared in the class body, then each new instance receives its own private slot. Methods in that same class can read and write the slot with this.#name. Private methods and private getters follow the same access rule.

BankAccount safe: use the public buttons
BankAccount classPop out in the code editor (opens in a new tab)JavaScript
class BankAccount {  #balance = 100;   deposit(amount) {    this.#validate(amount);    this.#balance += amount;    return this.balanceLabel;  }   withdraw(amount) {    this.#validate(amount);    this.#balance -= amount;    return this.balanceLabel;  }   get balanceLabel() {    return "₹" + this.#balance;  }   #validate(amount) {    if (amount < 0) throw new RangeError("No negative amounts");  }} const account = new BankAccount();// account.#balance is a SyntaxError outside the class.account["#balance"] = 999; // a normal public property
Runtime viewAda
Public ownerAda
Balance via method₹100
Public ["#balance"]missing
Brand checktrue

The safe is inside the object. The buttons are class methods that know the combination; outside code can only tape a separate label named #balance onto the outside.

Try it yourself

Start at ₹100. Use the methods; the private field can only change through class code.

A real class instance lives in this playground. The SyntaxError message is not evaluated in the browser; the lesson test proves it with Node's vm parser.

Try the buttons. The public methods change the real private balance. The Try account.#balance button shows the SyntaxError that our tests prove with a parser. The fake bracket property demonstrates the key distinction: account["#balance"] is just a normal public property whose string key happens to contain a hash mark.

Step through a private balance update
Step 0 of 8Ready
Your turn: follow the blue line

Predict the printed balance. Then step through how public methods reach private state.

Running in
  1. script
Next: line 24
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
class BankAccount {  #balance = 0;   constructor(opening) {    this.#validate(opening);    this.#balance = opening;  }   get balanceLabel() {    return "₹" + this.#balance;  }   deposit(amount) {    this.#validate(amount);    this.#balance += amount;    return this.balanceLabel;  }   #validate(amount) {    if (amount < 0) throw new RangeError("Amount must be positive");  }} account.deposit(25);console.log(account.balanceLabel);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Change line 24: how much does deposit add?

Changing the call starts a fresh replay. Predict the final printed label again.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
SyntaxErrors happen before runtime

Code like account.#balance outside the class is not a failed property lookup. It is invalid syntax. Duplicate private names in the same class are invalid syntax too. By contrast, a method that tries to read its private field from the wrong object parses successfully, then throws a TypeError when it runs.

Outside access: SyntaxErrorPop out in the code editor (opens in a new tab)JavaScript
class Account { #balance = 100; }const account = new Account();account.#balance;
Wrong object: runtime TypeErrorPop out in the code editor (opens in a new tab)JavaScript
class Account {  #balance = 100;  read(other) {    return other.#balance;  }}new Account().read({});
Accessible here?
  • Inside deposit(): this.#balance += amount
  • Inside withdraw(): this.#validate(amount)
  • Outside the class: account.#balance
  • In a subclass method: this.#balance
  • Outside: account["#balance"]
  • JSON.stringify(account)
Try it yourself
0 of 6 correct

Sort each code location. The answer turns on whether the code is inside the declaring class body, outside it, or only touching a public look-alike property.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Private static members

CLASS STATE

static #count belongs to the class itself, not to each instance. Use private static fields for class-wide counters, registries, caches, and helper methods that should not be part of the public API.

Private static countPop out in the code editor (opens in a new tab)JavaScript
class Vault {  static #count = 0;  static #lastId = 0;  #id;   constructor() {    Vault.#count += 1;    this.#id = ++Vault.#lastId;  }   static get count() {    return Vault.#count;  }   get label() {    return "vault-" + this.#id;  }} const first = new Vault();const second = new Vault();console.log(Vault.count);console.log(first.label + ", " + second.label);

The two vault instances have separate private instance IDs, but they share one private static count. Public code can ask Vault.count; it cannot read Vault.#count.

A surprising inherited static case

Static methods are inherited, and inside a static method this is the class used for the call. That is useful for public statics, but private static fields are branded to the class that declared them. If an inherited static method uses this.#count and a subclass calls it, V8 throws TypeError: Cannot read private member #count from an object whose class did not declare it. Name the declaring class, such as BaseVault.#count, when that is what you mean.

Inherited static method and private static brandPop out in the code editor (opens in a new tab)JavaScript
class BaseVault {  static #count = 1;  static readWithThis() {    return this.#count;  }  static readWithName() {    return BaseVault.#count;  }}class ChildVault extends BaseVault {}console.log(BaseVault.readWithThis());try {  ChildVault.readWithThis();} catch (error) {  console.log(error.name + ": " + error.message);}console.log(ChildVault.readWithName());

#x in obj brand checks

STEP THROUGH

Sometimes you want to know whether a value is one of your class’s instances without reading private data. Inside the class, #balance in value asks “does this object carry the private brand installed by this class?” It returns a boolean and does not open the safe.

Brand check without opening the safe
Step 0 of 7Ready
Your turn: follow the blue line

Brand checks ask whether this object carries the private brand; they do not read the private value.

Running in
  1. script
Next: line 8
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
class BankAccount {  #balance = 0;  static isAccount(value) {    return typeof value === "object" && value !== null && #balance in value;  }} const fake = { "#balance": 999 };console.log(BankAccount.isAccount(account));console.log(BankAccount.isAccount(fake));fake["#balance"] = 123;console.log(BankAccount.isAccount(fake));console.log(fake["#balance"]);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Real-life analogyA brand check is checking the maker’s stamp

A bank clerk can ask, “is this one of our folders?” by checking the stamp on the folder. They do not need to open the safe or count the money.

In real life: A stamped bank folder
In JavaScript: An object with the class’s private brand
In real life: A fake folder with a note saying balance
In JavaScript: A plain object with "#balance" as a public key
In real life: Checking the stamp without opening the safe
In JavaScript: #balance in obj returning true or false

Where the analogy stops: The brand check only proves that this private field was installed. It does not prove business validity, freshness, permissions, or that public properties were not changed.

Private fields also stay out of ordinary reflection. They do not appear in Object.keys, object spread, or JSON.stringify. But if you add a public property named "#balance", that public property is enumerable like any other.

Reflection sees public keys, not private fieldsPop out in the code editor (opens in a new tab)JavaScript
class Account {  #balance = 100;  owner = "Ada";  label() {    return "₹" + this.#balance;  }}const account = new Account();console.log(Object.keys(account).join(","));console.log(JSON.stringify(account));account["#balance"] = 999;console.log(Object.keys(account).join(","));console.log(JSON.stringify(account));

Private fields vs closures

COMPARE

You already know three ways to hide state. Closures hide variables in a function’s remembered scope. WeakMaps hide data in a side table keyed by objects. Private fields hide state in class instances with syntax the language enforces.

Choosing a privacy pattern
QuestionPrivate fieldsClosuresWeakMap privacy
Privacy strengthLanguage enforced; outside obj.#x is a syntax errorPrivate as long as you never expose the closed-over variablePrivate as long as the WeakMap stays hidden
Memory shapeState lives on each instance in private slotsEach factory call keeps an environment aliveState lives in a side table keyed by objects
InheritanceSubclasses cannot access the parent’s private names directlyNo class inheritance unless you build itMethods can share the module-level WeakMap if you design it that way
Debugging visibilityDevTools may show private slots, but code still cannot read themUsually appears as closure variablesUsually appears as a WeakMap entry or hidden side table
Object.keys / JSONSkippedNot object propertiesSkipped because data is outside the object

Prefer private fields when you are already designing a class and each instance needs its own protected state. Prefer closures for small factories, function modules, and one-off remembered values. Prefer WeakMaps when you need to attach hidden data to objects you do not control, or when a module-level side table is clearer than changing a class.

Where you’ll use this

Private members are not only for bank accounts. They are useful whenever you need to protect invariants: rules that must stay true for an object to work.

  • UI widgets: keep internal DOM references private and expose methods like open() or close().
  • Data models: validate writes through methods so impossible states cannot be assigned directly.
  • SDK clients: hide retry counters, caches, and normalized configuration.
  • Factories: combine private static caches with public factory methods.
Privacy is not secrecy from the user

Do not store passwords, API secrets, or security decisions in browser JavaScript and assume #private protects them from the person running the browser. Private fields protect your program’s object boundaries, not secrets from the machine owner.

Common misconceptions

“An underscore makes a property private.”

No. _balance is a public property with a warning label. #balance is a language-enforced private name.

“Private fields are just strings with # in the name.”

No. account["#balance"] reads a public string key. It cannot touch #balance.

“Subclasses can read parent private fields.”

No. A subclass inherits public and protected-by-convention methods, but it is a different class body and cannot spell the parent’s private names.

“Brand checks read private data.”

No. #x in obj only returns whether the object has that private brand.

“Private fields are included in JSON.”

No. JSON and Object.keys see public enumerable properties only.

Practice: lock the right doors

5 EXERCISES
Exercise 1 · Warm-upConvert an underscore field

Rewrite a Timer that used _seconds so it uses #seconds. What should the program print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
class Timer {
  #seconds = 0;
  tick() {
    this.#seconds += 1;
    return this.#seconds;
  }
}
const timer = new Timer();
console.log(timer.tick());
console.log(Object.keys(timer).length);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticePredict outside access

    Predict whether this prints a number, returns undefined, or errors.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    class Account { #balance = 100; }
    const account = new Account();
    account.#balance;

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeAdd a brand check

      Implement Member.isMember(value) so it recognizes real instances without reading the private value.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      class Member {
        #id = 1;
        static isMember(value) {
          return typeof value === "object" && value !== null && #id in value;
        }
      }
      console.log(Member.isMember(new Member()));
      console.log(Member.isMember({ "#id": 1 }));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeWrite a private validator

        Add a private method that rejects empty cart item names.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        class Cart {
          #items = [];
          add(name) {
            this.#requireName(name);
            this.#items.push(name);
            return this.#items.length;
          }
          #requireName(name) {
            if (name === "") throw new RangeError("Name required");
          }
        }
        const cart = new Cart();
        console.log(cart.add("Tea"));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeChoose the privacy tool

          You are building a VideoPlayer class with play(), pause(), and an internal current time that must only change through validation. Which privacy pattern best fits?

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS
            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What does #balance mean in a class?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this account program print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              class Account {
                #balance = 100;
                deposit(amount) {
                  this.#balance += amount;
                  return this.#balance;
                }
              }
              const account = new Account();
              console.log(account.deposit(25));

              Choose an answer to see the explanation.

            3. Question 3 of 7What happens if code outside the class writes account.#balance?

              Choose an answer to see the explanation.

            4. Question 4 of 7What do the brand-check lines print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              class Account {
                #balance = 0;
                static isAccount(value) {
                  return typeof value === "object" && value !== null && #balance in value;
                }
              }
              console.log(Account.isAccount(new Account()));
              console.log(Account.isAccount({ "#balance": 0 }));

              Choose an answer to see the explanation.

            5. Question 5 of 7What do Object.keys and JSON print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              class Account {
                #balance = 100;
                owner = "Ada";
              }
              const account = new Account();
              console.log(Object.keys(account).join(","));
              console.log(JSON.stringify(account));

              Choose an answer to see the explanation.

            6. Question 6 of 7Which static private access is safest inside a base class static method?

              Choose an answer to see the explanation.

            7. Question 7 of 7When are closures a better privacy fit than private fields?

              Choose an answer to see the explanation.

            Key takeaways

            • #private names are real private class members, not naming conventions.
            • Only the declaring class body can use a private field, method, getter, setter, or static member.
            • Outside account.#x and duplicate private declarations are SyntaxErrors; wrong-object private reads are runtime TypeErrors.
            • #x in obj checks the private brand without reading the private value.
            • Private fields, closures, and WeakMaps are all useful privacy tools; choose by code shape.

            Remember the one-liner.
            A #private member is a real language lock: class methods know the combination; outside code does not.

            Up next: Extending built-ins & instanceof.

            CompleteFrontend Clear concepts. Working examples.