Start learning free

LESSON 181 OF 344 · OBJECT-ORIENTED PROGRAMMING

Access Modifiers in JavaScript: Private # Fields

Lesson Read the full explanation

TL ; DR:

  • Modern JavaScript (ES2022+) has true private fields with the # prefix:

    class Dog {
        #name;
    
        constructor(name) {
            this.#name = name;
        }
    
        getName() {
            return this.#name;
        }
    }
    

  • Outside the class, rex.#name is a syntax error — the runtime enforces it.

  • Older code uses the underscore convention (this._name), which is just a hint and not enforced.



Full lesson:

For decades, JavaScript classes had no real privacy — programmers used a leading underscore (_name) as a convention. ES2022 added true private fields with the # prefix.


How # private fields work

  • You must declare the field at the top of the class body: #name;
  • You access it as this.#name — never as a string or via this[...].
  • Trying to access from outside is a syntax error, not a runtime error.

Your task

The Dog class declares #name and the constructor stores into it. Add a getName() method that returns this.#name.

Dog's name: Rex
STEP 1 OF 2
    Ctrl + Enter to check

    JAVASCRIPT · OBJECT-ORIENTED PROGRAMMING

    Access Modifiers in JavaScript, explained

    Why hide data

    By default, every property of a JavaScript object is public: any code that holds the object can read it and overwrite it. That is convenient, but it lets outside code put an object into a state its author never intended, such as a negative age. Hiding a field and exposing it only through methods lets the class keep control of its own data. This idea is called encapsulation.

    Some languages express it with keywords such as public and private, called access modifiers. JavaScript has no public or private keywords. It has a prefix instead.

    The underscore convention

    For many years JavaScript classes had no real privacy. Programmers began a property's name with an underscore, as in this._name, to tell readers "this is internal, leave it alone". That is only a hint. Nothing stops outside code from reading or changing it:

    class Dog {
        constructor(name) {
            this._name = name;
        }
    }
    
    let rex = new Dog("Rex");
    rex._name = "changed";
    console.log(rex._name);   // changed
    

    You will still meet this style in older code, but it protects nothing.

    True private fields with #

    Modern JavaScript (ES2022 and later) has real private fields. Put # in front of the name:

    class Dog {
        #name;
    
        constructor(name) {
            this.#name = name;
        }
    
        getName() {
            return this.#name;
        }
    }
    

    The rules are strict:

    • Declare the field first. The line #name; at the top of the class body is required. Using this.#name without declaring it is a SyntaxError, and a private field can't be created later by assignment the way an ordinary property can.
    • The # is part of the name. Always write it as this.#name. It is not a string, so this["#name"] doesn't reach it: that is an ordinary property that happens to have # in its name, and it is undefined here.
    • Only the class body can use it. Code outside the class that writes rex.#name doesn't compile: it is a syntax error, found before the program runs, and not a runtime failure. A subclass can't use its parent's private field either.

    A private field also stays out of sight. Object.keys(rex) gives an empty list, JSON.stringify(rex) gives {} and console.log(rex) prints Dog {}.

    Using private fields well

    A private field is usually paired with methods that control access to it. A getter method returns the value, and a setter changes it after checking it:

    class Dog {
        #age = 0;
    
        setAge(value) {
            if (value < 0) {
                throw new RangeError("age must not be negative");
            }
            this.#age = value;
        }
    
        getAge() {
            return this.#age;
        }
    }
    

    Now no code can give a dog a negative age. The same # prefix works on methods, so #helper() { ... } defines a private method that only the class itself can call.

    The exercise, step by step

    The starter code declares #name and stores into it in the constructor, then calls rex.getName(). That method doesn't exist yet.

    1. Declare the method. Below the constructor, inside the class, write an empty getName() { }.
    2. Return the field. In the body write return this.#name;. Because getName belongs to the class, it is allowed to read the private field, and the caller receives the value.

    The solution in JavaScript

    class Dog {
        #name;
    
        constructor(name) {
            this.#name = name;
        }
    
        getName() {
            return this.#name;
        }
    }
    
    let rex = new Dog("Rex");
    console.log("Dog's name: " + rex.getName());
    

    The output is:

    Dog's name: Rex
    

    Common mistakes

    • Forgetting the declaration. Without #name; in the class body, this.#name = name fails with a SyntaxError saying the private field must be declared in an enclosing class.
    • Dropping the # inside the class. this.name is a different, public property. A class can have both name and #name, and mixing them up leaves getName() returning undefined.
    • Reading it from outside. rex.#name is a syntax error. Add a method such as getName() instead.
    • Expecting privacy from the underscore. _name is a naming habit and nothing more.

    Key takeaways

    • Properties are public by default, and JavaScript has no private keyword.
    • A name starting with # is a private field, enforced by the language.
    • Declare it in the class body, and read and write it only as this.#name inside the class.
    • Pair private fields with getter and setter methods to control how the data is used.
    Keep going with JavaScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free