completefrontend
Open lab My progress
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
    Keep class data privateUse # fields, methods, and getters that only code inside the class can use.
  • 02
    Check who made an objectUse #x in obj to ask whether your class made an object, without reading its private data.
  • 03
    Pick a way to hide dataCompare private fields with closures and WeakMaps, and pick the one that fits.

Real privacy for class objects

Most JavaScript objects are open. Say an object has a property named balance. Any other code can read it, change it, or delete it. Other code can also overwrite it by mistake.

Some programmers name it _balance instead. The underscore is only a habit. It is a polite sign that says "please don't touch". Nothing stops other code from touching it.

A private member is different. Its name starts with #, like #balance or #validate(). Only code written inside the class can use that name. "Inside the class" means between the class's curly braces { }. This lesson calls that part the class body.

The # is part of the name itself. It is not a normal property name written as text. Code outside the class can't even write account.#balance. JavaScript refuses to run a program that contains it.

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

Picture a bank folder with a small safe inside it. The bank staff know the secret number that opens the safe. Anyone else can hold the folder, pass it on, or ask the staff to do something with it. But they can't open the safe themselves.

In real life: The safe inside the bank folder
In JavaScript: The private field #balance on the object
In real life: Bank staff who know the safe's secret number
In JavaScript: Methods written inside the class
In real life: A sticky note that says "do not touch"
In JavaScript: A normal property named _balance
In real life: Checking that a folder came from this bank
In JavaScript: A check like #balance in obj (you'll see it later)
The main idea

A private member is a field, method, getter, setter, or static member whose name starts with #. Only code inside the class body that creates it can use it.

Here is what each of those words means:

  • A field is a value that each object keeps, like #balance = 0.
  • A method is a function on the object, like #validate().
  • A getter is a method you read like a property, without (). You write it with get.
  • A setter is a method that runs when you assign a value. You write it with set.
  • A static member belongs to the class itself, not to each object.

This lesson uses the class syntax from Class basics. You have hidden data in other ways before, in Closures, Closure patterns, and WeakMap & WeakSet. Near the end, you'll compare those ways with private fields.

#private fields & methods

INTERACTIVE

You create a private field in the class body, for example #balance = 0;. Each new object then gets its own private #balance. Methods in the same class read and change it with this.#balance. Private methods and private getters follow the same rule.

The name is fixed when you write the class. You can't build it from text with square brackets. So this["#balance"] never means the private field. It means a normal property whose name happens to start with #.

A bank account: change the balance with the public methods
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
What the object holdsAda
Public ownerAda
Balance, read by a method₹100
Public ["#balance"]missing
Made by BankAccount?true

The private balance is inside the object. Only the class's methods can change it. Code outside can only add a separate public property named "#balance".

Try it yourself

The balance starts at ₹100. Only the class's own methods can change the private field.

A real BankAccount object runs in this playground. The SyntaxError button only shows the message: the browser can't run that line, so the lesson tests check it instead.

Try the buttons:

  • Deposit and Withdraw call public methods. They change the real private balance.
  • Try account.#balance shows the error you get when code outside the class writes that. It is a SyntaxError: JavaScript refuses to run the program at all.
  • Add fake ["#balance"] adds a normal public property. Its name is the text "#balance". The real private balance does not change.
Step through a private balance update
Step 0 of 8Ready
Your turn: follow the blue line

Guess the printed balance first. Then step through how public methods change the private field.

Running in
  1. script
Next: line 24
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);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Change line 25: how much does deposit add?

Changing the amount starts the replay again. Guess the printed balance before you step.

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.

A method or a getter can be private too. Below, the getter's name, #label, is private. The public method label() can use it. Code outside the class can't write user.#label.

A private getterPop out in the code editor (opens in a new tab)JavaScript
class User {  #name = "Asha";  get #label() {    return "User: " + this.#name;  }  label() {    return this.#label;  }}console.log(new User().label());

Line 2 stores the name in a private field. Lines 3–5 make a private getter. Line 7 reads that getter from inside the class. Line 10 calls the public method, which prints User: Asha.

Two different errors

Before the program runs: writing account.#balance outside the class is a SyntaxError. So is creating the same private name twice in one class. JavaScript checks the whole program first and refuses to start it.

While the program runs: a method inside the class may read other.#balance. That is allowed to write. But if other was not made by this class, it has no #balance. JavaScript then throws a TypeError at that moment.

Left: inside the class, this.#balance reaches the account's #balance: 100 card, so it is allowed. Right: outside the class, account.#balance is a SyntaxError.this.#balanceinside the class#balance: 100accountallowedaccount.#balanceoutside the class#balance: 100accountSyntaxError
Both sides point at the same #balance. Code inside the class may use it. Code outside the class can't even write it, so JavaScript won't run the program.
Outside the class: SyntaxErrorPop out in the code editor (opens in a new tab)JavaScript
class Account { #balance = 100; }const account = new Account();account.#balance;
Wrong object: TypeError while runningPop out in the code editor (opens in a new tab)JavaScript
class Account {  #balance = 100;  read(other) {    return other.#balance;  }}new Account().read({});

In the first example, line 3 is outside the class, so the program never starts. In the second example, line 4 is inside the class, so it is allowed. Line 7 passes a plain object {}, which has no #balance, so line 4 throws when it runs.

Can this code use the private field?
  • Inside deposit(): this.#balance += amount
  • Inside withdraw(): this.#validate(amount)
  • Outside the class: account.#balance
  • In a child class method: this.#balance
  • Outside: account["#balance"]
  • JSON.stringify(account)
Try it yourself
0 of 6 correct

Sort each piece of code. Ask where it is written: inside the class that created the private name, outside it, or on a normal public property that only looks similar.

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

Private static members

CLASS STATE

A static member belongs to the class itself, not to each object. So static #count is one private value for the whole class. Private static fields are good for things the whole class shares and callers shouldn't touch:

  • a counter of how many objects were made,
  • a list of all the objects made so far,
  • a store of saved results, so the same work isn't done twice,
  • helper methods that only the class should call.

Each private name can appear only once in a class. A class can't have both #count and static #count; that is a SyntaxError. Give them different names, like #id for each object and static #count for the class. Then write this.#id for one object's value and Vault.#count for the class's value.

One private count for the whole classPop 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);

Line 7 adds 1 to the class's #count each time an object is made. Line 8 gives each object its own #id. After two objects, line 22 prints 2 and line 23 prints vault-1, vault-2. Code outside the class can read Vault.count, the public getter on line 11. It can't read Vault.#count.

The Vault class holds one private card, #count: 2. Below it are two objects. first holds #id: 1 and second holds #id: 2. An arrow marked +1 goes from each object up to the class.the class itself#count: 2Vault+1+1#id: 1first#id: 2second
The class Vault holds the only #count. Each new Vault() adds 1 to it and gets its own #id.
Watch out for this in an inherited static method

A child class (made with extends) also gets its parent's static methods. Inside a static method, this is the class you called it on. But a private static field exists only on the class that created it.

So when a child class calls an inherited method that reads this.#count, this is the child. The child has no #count. In Chrome and Node, JavaScript throws TypeError: Cannot read private member #count from an object whose class did not declare it. To avoid this, name the class that owns the field, like BaseVault.#count.

A child class calls an inherited static methodPop 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());

Line 11 calls readWithThis() on BaseVault and prints 1. Line 13 calls it on ChildVault. Now this is ChildVault, so it throws, and line 15 prints the error. Line 17 calls readWithName(), which names BaseVault directly, so it prints 1.

#x in obj brand checks

STEP THROUGH

Sometimes you want to know: was this object made by my class? When a class makes an object, it puts its private fields on it. Those private fields work like the class's mark, or brand. A brand check looks for that mark.

Inside the class, #balance in value asks: does value have this class's #balance? The answer is true or false. The check never reads the balance itself.

Check that the value is an object first, because #balance in null throws an error. typeof null is "object", so you also need value !== null. The && operator stops at the first part that is false, so the brand check only runs on real objects.

A brand check only tells you which class made the object. It doesn't check that the balance or any other value makes sense.

Ask which objects the class made
Step 0 of 7Ready
Your turn: follow the blue line

A brand check asks whether this class made the object. It never reads the private value.

Running in
  1. script
Next: line 8
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"]);
CallStoreChangeResultBlue line: runs next. 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.
#balance in value, asked about two objects. The account object holds the private field #balance: 0, so the answer is true. The fake object only holds a public key "#balance": 999, so the answer is false.#balance in value#balance: 0accountprivate fieldtrue"#balance": 999fakepublic keyfalse
The check looks for the private field that the class puts on its objects. account has it. fake only has a public key with the same text.
Real-life analogyA brand check is like looking for the bank's stamp

A bank clerk wants to know, "Is this one of our folders?" She looks for the bank's stamp on the folder. She doesn't need to open the safe or count the money.

In real life: A folder with the bank's stamp
In JavaScript: An object made by the class, with its private fields
In real life: A fake folder with "balance" written on a note
In JavaScript: A plain object with a public "#balance" key
In real life: Looking at the stamp without opening the safe
In JavaScript: #balance in obj gives true or false

Private fields also stay hidden from tools that list an object's properties. Object.keys, the spread { ...obj }, and JSON.stringify all skip them. A public property named "#balance" is different: it is a normal property, so these tools show it.

Object.keys and JSON skip 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));

Lines 9 and 10 show only owner. Line 11 adds a public property named "#balance". Now lines 12 and 13 show it, with 999. The private #balance, which holds 100, never appears.

Private fields vs closures

COMPARE

You now know three ways to hide data:

  • Closures. A function remembers the variables around it. Code outside the function can't reach those variables.
  • WeakMaps. You keep the hidden data in a separate map. Each object is a key in that map.
  • Private fields. The data sits on each object, and JavaScript itself blocks code outside the class.

In the table below, each row asks one question. Read across the row to compare the three ways.

Three ways to hide data, compared
QuestionPrivate fieldsClosuresWeakMap
How strong is the privacy?JavaScript itself blocks it. Writing obj.#x outside the class is a SyntaxError.Private, as long as you never hand out the hidden variable.Private, as long as you keep the WeakMap hidden.
Where is the data kept?On each object, in its private fields.In the variables that the function remembers.In a separate map, with each object as a key.
Can a child class use it?No. A child class can't write the parent's private names.There are no classes here, unless you build them yourself.Yes, if both classes can see the same WeakMap.
What do the browser's developer tools show?They may show the private fields, but your code still can't read them.Usually the remembered variables of the function.Usually the WeakMap and its entries.
Do Object.keys and JSON show it?No. They skip private fields.No. The data isn't a property of any object.No. The data is outside the object.

When to pick each one:

  • Pick private fields when you are writing a class and each object needs its own hidden data.
  • Pick closures for a factory (a function that builds and returns something), or for a value one function needs to remember.
  • Pick a WeakMap when you need to attach hidden data to objects that other code made and you can't change.

Private fields are not always the best choice. A closure can hide data without any object at all. A WeakMap can keep data beside objects that another file made. Pick the way that matches who owns the data and what callers should see.

Where you'll use this

Private members help whenever an object has a rule that must always stay true. For example, a balance should never go below zero. If only the class's methods can change the balance, they can check the rule every time.

  • Page widgets, like a drop-down menu: keep the page elements it controls private. Offer methods like open() and close().
  • Data objects: check every change inside a method, so a wrong value can't be written directly.
  • Code that talks to a web service for you: hide the retry counter, saved results, and settings.
  • Factories: keep a private static store of objects already made, and offer a public method that makes new ones.
A todo that can only be finished through a methodPop out in the code editor (opens in a new tab)JavaScript
class Todo {  #done = false;  finish() {    this.#done = true;  }  get status() {    return this.#done ? "done" : "open";  }}const todo = new Todo();todo.finish();console.log(todo.status);

Line 2 creates a private #done flag. Line 4, inside the public finish() method, sets it to true. The getter on line 7 shows only the status text. Line 11 calls finish(), and line 12 prints done. Callers can't set todo.#done themselves.

Private doesn't mean secret

Don't put passwords or secret keys in browser JavaScript, even in a #private field. The person using the browser can still see them, for example in the browser's developer tools. Private fields stop other parts of your program from touching the data. They don't hide it from the person who runs the program.

Common misconceptions

"An underscore makes a property private."

No. _balance is a normal public property with a warning in its name. #balance is private, and JavaScript blocks code outside the class from using it.

"Private fields are just names with # at the start."

No. account["#balance"] reads a normal public property whose name is the text "#balance". It can't reach the private #balance.

"Child classes can read the parent's private fields."

No. A child class (made with extends) gets the parent's methods. But its own class body is a different class body, so it can't write the parent's private names.

"Brand checks read private data."

No. #x in obj only answers true or false: does this object have the private field? It never gives you the value.

"Private fields are included in JSON."

No. JSON.stringify and Object.keys only see public properties. They skip private fields.

Here is the underscore version. It looks like private data, but nothing protects it. Any caller can change the list directly and skip the class's method.

An underscore field is still publicPop out in the code editor (opens in a new tab)JavaScript
class Cart {  _items = [];  add(item) {    this._items.push(item);  }}const cart = new Cart();cart.add("tea");cart._items.push("book");console.log(cart._items.join(","));

Line 8 adds tea through the add() method. Line 9 skips the method and pushes book straight into the list. Line 10 prints tea,book. With #items instead, line 9 would be a SyntaxError.

A child class uses the parent's public methodPop out in the code editor (opens in a new tab)JavaScript
class Parent {  #value = 7;  read() {    return this.#value;  }}class Child extends Parent {}console.log(new Child().read());

Line 8 prints 7. Child has no read() of its own, so it uses the one from Parent. That method was written inside Parent, so line 4 may read #value. The child can call the method, but it still can't write #value itself.

If the child class tries to write the parent's private name itself, JavaScript refuses to run the code:

A child class can't write the parent's private namePop out in the code editor (opens in a new tab)JavaScript
class Parent {  #value = 7;}class Child extends Parent {  readParent() {    return this.#value;  }}

Practice: keep the right data private

5 EXERCISES
Exercise 1 · Warm-upTurn an underscore field into a private field

This Timer used to keep its time in _seconds. Now it uses #seconds. What does 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 what outside code does

    Does this print a number, give undefined, or cause an error? If it is an error, which kind?

    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

      Member.isMember(value) should give true only for real Member objects, without reading the private value. Read how it does that. What do the two lines print?

      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 · PracticeUse a private check method

        This cart uses a private method, #requireName, to stop empty item names. Try changing "Tea" to "" to see it throw. As written, what does the program print?

        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 · ChallengePick the way to hide data

          You are building a VideoPlayer class with play() and pause() methods. It also keeps the current time, and that time may only change after a check. Which way of hiding data fits best?

          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 7A static method in the class Base reads a private static field. Child classes may call this method too. Which line is safest?

              Choose an answer to see the explanation.

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

              Choose an answer to see the explanation.

            Key takeaways

            • A # name is truly private. It is not just a naming habit like _balance.
            • Only the class body that creates a private field, method, getter, setter, or static member can use it.
            • Writing account.#x outside the class, or creating the same private name twice, is a SyntaxError: the program won't start.
            • Reading a private field from an object the class didn't make is a TypeError while the program runs.
            • #x in obj tells you whether the class made the object. It doesn't read the private value.
            • Private fields, closures, and WeakMaps all hide data. Pick the one that fits your code.

            Remember the one-liner.
            A #private member can only be used by code inside its class, and JavaScript itself blocks everyone else.

            Up next: Extending built-ins & instanceof.

            CompleteFrontend Clear concepts. Working examples.