JavaScript Encapsulation

Encapsulation is one of the fundamental concepts of Object-Oriented Programming (OOP). It means grouping data and the methods that operate on that data into a single unit, such as a class, while controlling how the internal data can be accessed or modified.

In JavaScript, encapsulation helps protect an object’s internal state from unintended changes. You can use private class fields, methods, getters, and setters to control how other parts of your program interact with an object.

For example, a bank account should not allow anyone to change its balance to an invalid value directly. Encapsulation allows you to define rules for depositing and withdrawing money.

In this tutorial, you will learn what encapsulation is, why it is important, and how to implement it using JavaScript classes.

What Is Encapsulation in JavaScript?

Encapsulation is the practice of combining related data and behavior within an object and controlling access to its internal state.

Consider a bank account with a balance. If the balance can be changed without any restrictions, a program might accidentally assign a negative value or bypass important validation.

Encapsulation lets you keep the balance private and provide methods that validate changes.

Example Without Encapsulation

class BankAccount {
  constructor(balance) {
    this.balance = balance;
  }
}

const account = new BankAccount(1000);

account.balance = -500;

console.log(account.balance);

Output:

-500

The balance can be changed directly, even though a negative balance might be invalid for this account.

Example With Encapsulation

class BankAccount {
  #balance;

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

  deposit(amount) {
    if (amount <= 0) {
      throw new Error("Deposit must be greater than zero");
    }

    this.#balance += amount;
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount(1000);

account.deposit(500);

console.log(account.getBalance());

Output:

1500

In this example, #balance is a private field. It cannot be accessed directly from outside the class. The deposit() method controls how the balance changes, and getBalance() provides read access.

This helps maintain the object’s internal state.

Why Is Encapsulation Important?

Encapsulation makes JavaScript applications easier to maintain and less vulnerable to accidental data changes.

Its main benefits include:

  • Data protection: Private fields prevent direct access from outside the class.
  • Controlled modification: Methods can validate values before changing internal state.
  • Better organization: Data and related behavior stay together.
  • Maintainability: Internal implementation can change without requiring every caller to change.
  • Consistency: The object can enforce rules that keep its state valid.

Encapsulation does not automatically make an application secure. Sensitive data and security rules must also be protected at the appropriate system boundaries, especially on servers.

Private Fields in JavaScript

JavaScript supports private class fields using the # prefix. A private field can only be accessed from the class body where it is declared, including permitted methods and accessors.

Example of a Private Field

class User {
  #password;

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

  checkPassword(password) {
    return this.#password === password;
  }
}

const user = new User("secret123");

console.log(user.checkPassword("secret123"));
console.log(user.checkPassword("wrong123"));

Output:

true
false

The #password field stores the password value privately. The checkPassword() method provides a controlled way to compare a supplied value with it.

For a real application, passwords should not be stored as plain text. Authentication systems should use appropriate password hashing and secure verification.

Accessing a Private Field Directly

class User {
  #name = "Karim";
}

const user = new User();

console.log(user.#name);

Result: This code produces a syntax error because #name is private to the class body.

You cannot bypass this restriction by using ordinary bracket notation, such as user["#name"].

Using Methods to Control Data

Methods allow you to define rules for accessing or changing private data.

Example of Data Validation

class Product {
  #price;

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

  setPrice(price) {
    if (price < 0) {
      throw new Error("Price cannot be negative");
    }

    this.#price = price;
  }

  getPrice() {
    return this.#price;
  }
}

const product = new Product(1000);

product.setPrice(1500);

console.log(product.getPrice());

Output:

1500

If you try to set a negative price, the method throws an error instead of updating the private field.

product.setPrice(-500);

Output: An error is thrown with the message Price cannot be negative.

The important point is that validation happens inside the class, where the internal data is managed.

Getters and Setters in Encapsulation

JavaScript provides getters and setters to control how properties are read and modified.

  • A getter allows a property to be read through a method-like accessor.
  • A setter allows a property to be assigned through a method-like accessor.

These accessors are useful when you want a natural property interface while keeping the underlying data private.

Example of a Getter and Setter

class Employee {
  #salary;

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

  get salary() {
    return this.#salary;
  }

  set salary(value) {
    if (value < 0) {
      throw new Error("Salary cannot be negative");
    }

    this.#salary = value;
  }
}

const employee = new Employee(30000);

console.log(employee.salary);

employee.salary = 35000;

console.log(employee.salary);

Output:

30000
35000

Notice that the getter and setter are used like a property:

employee.salary;
employee.salary = 35000;

You do not need to call them as employee.salary() or employee.salary(35000).

When Should You Use Getters and Setters?

Use getters and setters when reading or updating a property needs validation, calculated values, or other controlled behavior.

However, not every property needs a getter or setter. For simple data that does not require restrictions, ordinary public properties may be sufficient.

Complete Example of Encapsulation in JavaScript

The following example demonstrates a bank account with private data, validation, and controlled operations.

class BankAccount {
  #owner;
  #balance;

  constructor(owner, balance) {
    if (balance < 0) {
      throw new Error("Initial balance cannot be negative");
    }

    this.#owner = owner;
    this.#balance = balance;
  }

  get owner() {
    return this.#owner;
  }

  get balance() {
    return this.#balance;
  }

  deposit(amount) {
    if (amount <= 0) {
      throw new Error("Deposit must be greater than zero");
    }

    this.#balance += amount;
  }

  withdraw(amount) {
    if (amount <= 0) {
      throw new Error("Withdrawal must be greater than zero");
    }

    if (amount > this.#balance) {
      throw new Error("Insufficient balance");
    }

    this.#balance -= amount;
  }
}

const account = new BankAccount("Karim", 2000);

account.deposit(500);
account.withdraw(300);

console.log(account.owner);
console.log(account.balance);

Output:

Karim
2200

How This Example Works

  • #owner and #balance are private fields.
  • The constructor validates the initial balance.
  • The getters provide read access to the owner’s name and balance.
  • deposit() accepts only positive deposit amounts.
  • withdraw() validates the amount and checks the available balance.
  • External code cannot directly assign a new value to #balance.

The class keeps the account’s internal state under its own control.

Encapsulation vs Abstraction

Encapsulation and abstraction are related OOP concepts, but they have different purposes.

EncapsulationAbstraction
Groups data and behavior and controls access to internal stateHides unnecessary implementation details and exposes essential functionality
Focuses on controlling how data is accessed or modifiedFocuses on what an object provides without requiring users to understand how it works
Uses private fields, methods, getters, and settersCan use public methods, interfaces, and well-designed APIs
Example: protecting a bank account balanceExample: using withdraw() without knowing the internal balance-update logic

Encapsulation helps implement abstraction by hiding internal details, but the two concepts are not identical.

Common Mistakes When Using Encapsulation

Using Public Properties for Data That Requires Protection

A public property can be changed directly from outside the class. Use a private field when the class needs to control access to its internal state.

Forgetting the # Prefix

Private fields must be declared and referenced using the # prefix.

class User {
  #name = "Karim";

  getName() {
    return this.#name;
  }
}

Writing this.name instead of this.#name refers to a different, ordinary property.

Adding Getters and Setters Without a Purpose

Getters and setters are not required for every property. Use them when they provide meaningful validation, calculated values, or control over access.

Assuming Encapsulation Automatically Validates Data

A private field protects access, but it does not automatically validate its value. The constructor and methods must implement the rules your application needs.

Confusing Private Fields with Underscore Naming

A property such as _balance is still publicly accessible in ordinary JavaScript. The underscore is only a naming convention.

By contrast, #balance is a genuine private class field enforced by the language.

FAQs About JavaScript Encapsulation

What is encapsulation in JavaScript?

Encapsulation is an OOP concept that combines related data and behavior in an object and controls how its internal state is accessed or changed.

How do you achieve encapsulation in JavaScript?

You can use classes, private fields beginning with #, methods, getters, and setters to control access to an object’s data.

What is a private field in JavaScript?

A private field is a class field declared with #. It can only be accessed from within the class body where it is declared.

What is the difference between a public property and a private field?

A public property can generally be accessed and modified from outside the object. A private field is restricted to its declaring class body.

Are getters and setters necessary for encapsulation?

No. They are useful when you need controlled property access, validation, or calculated values. Private fields and ordinary methods can also provide encapsulation.

What is an example of encapsulation in real life?

A bank account is a common example. The account balance is kept internal, while deposit and withdrawal operations control how it changes.

What are the advantages of encapsulation?

Encapsulation helps protect internal state from accidental changes, improves code organization, supports validation, and makes applications easier to maintain.

Conclusion

Encapsulation is an important JavaScript OOP concept that combines data and behavior while controlling access to an object’s internal state.

Private fields help prevent direct external access, methods enforce rules, and getters and setters provide controlled ways to read or update properties.

By applying encapsulation to classes such as bank accounts, users, and products, you can build JavaScript applications that are more organized, maintainable, and consistent.