In modern JavaScript (ES2022+), Private Methods are a critical feature of the class syntax that allows developers to define functions that are only accessible from within the class itself. By prefixing a method name with the hash symbol #, the JavaScript engine enforces a strict boundary, preventing these methods from being called, viewed, or even detected by external scripts or inheriting subclasses.

This mechanism is the cornerstone of Encapsulation, enabling you to hide the “how” (internal implementation details) while only exposing the “what” (the public API).

1. Syntax and Implementation

To create a private method, you must include the # prefix in the method definition within the class body. Like private fields, they cannot be declared or added dynamically outside the class definition.

JavaScript
class SecureUploader {
  #connectionStatus = 'IDLE';

  // Public method: The "Interface"
  async uploadFile(file) {
    this.#validateFile(file);      // Internal check
    await this.#establishSocket();  // Internal setup
    console.log(`Uploading ${file.name}...`);
  }

  // Private method: Logic hidden from the user
  #validateFile(file) {
    if (file.size > 1024 * 1024) {
      throw new Error("File too large.");
    }
  }

  // Private method: Complex internal state management
  async #establishSocket() {
    this.#connectionStatus = 'CONNECTING';
    // ... networking logic ...
  }
}

const uploader = new SecureUploader();
uploader.uploadFile(myFile); // ✅ Works
// uploader.#validateFile(myFile); // ❌ SyntaxError: Private field '#validateFile' must be declared in an enclosing class

2. Static Private Methods

You can combine the static and # keywords to create private methods that belong to the class constructor rather than the instance. This is useful for utility functions that the class needs internally but shouldn’t be exposed as part of its public utility suite.

JavaScript
class Database {
  static #connectionCount = 0;

  static connect() {
    this.#logConnection();
    this.#connectionCount++;
  }

  static #logConnection() {
    console.log(`Establishing connection #${this.#connectionCount + 1}`);
  }
}

3. Interaction with Private Fields and Accessors

Private methods often work in tandem with Private Fields and Private Getters/Setters. This creates a fully encapsulated ecosystem where the internal “engine” of the class is shielded from the “dashboard” (public methods).

JavaScript
class UserProfile {
  #age;

  constructor(age) {
    this.#age = age;
  }

  get isAdult() {
    return this.#validateAge();
  }

  // Private Method for validation
  #validateAge() {
    return this.#age >= 18;
  }
}

4. Private Accessors (Getters and Setters)

You can also make Getters and Setters private. This is useful for providing a controlled way for other internal methods to read or modify private fields while still keeping that logic hidden from the outside.

JavaScript
class Database {
  #retryCount = 0;

  get #isOverloaded() {
    return this.#retryCount > 5;
  }

  connect() {
    if (this.#isOverloaded) {
      console.log("System overloaded. Aborting.");
      return;
    }
    // connection logic...
  }
}

5. Interaction with Inheritance

A defining characteristic of private methods is that they are not inherited. Even if a subclass extends a parent, it cannot call the parent’s private methods. This prevents “Fragile Base Class” syndromes, where changes to internal helpers in a parent class accidentally break child classes that were relying on those helpers.

JavaScript
class Parent {
  #internalLogic() {
    console.log("Parent's secret logic");
  }

  callSecret() {
    this.#internalLogic(); // ✅ Parent can call its own private method
  }
}

class Child extends Parent {
  attemptCall() {
    // this.#internalLogic(); // ❌ SyntaxError: Private field must be declared in an enclosing class
  }
}

6. Technical Nuances and Constraints

No “Protected” Scope

JavaScript does not currently have a “protected” keyword (accessible to subclasses but not the public). Private methods are hard private—they are not accessible even to classes that extend the parent class.

JavaScript
class Parent {
  #hidden() { console.log("Secret"); }
}

class Child extends Parent {
  reveal() {
    // this.#hidden(); // ❌ Error: #hidden is not accessible here
  }
}

The “In” Operator for Branding

You can check if an object has a specific private method using the in operator. This is a secure way to verify if an object is an instance of a specific class before attempting to interact with its internals (a pattern known as “branding”).

JavaScript
class Validator {
  #privateMethod() {}

  static isValidator(obj) {
    return #privateMethod in obj;
  }
}

Categorized in:

Javascript,