Glossary

Encapsulation

Encapsulation is the OOP principle of bundling data and the methods that operate on that data into a single unit (class), and restricting direct access to the internal state from outside code — exposing only a controlled public interface.

Explanation

Encapsulation serves two purposes: organization (keeping related data and behavior together in one place) and information hiding (preventing outside code from directly accessing or corrupting internal state). The second purpose is what makes encapsulation powerful: if outside code can only interact with your class through a defined public API, you can change the internal implementation freely without breaking callers. In JavaScript, private fields (with # prefix) are the modern way to enforce encapsulation. Any property without # is publicly accessible. Getters and setters (get property() {} / set property(value) {}) allow computed read/write access with validation logic. Before private fields, the convention was to prefix private members with _ (underscore), but this was only a convention — nothing prevented access. The practical benefit: if your BankAccount class exposes balance directly (this.balance = 1000), any code anywhere can corrupt it (account.balance = -999). If balance is private with a method to access it (get balance()) and another to modify it (deposit(amount) that validates amount > 0), the invariant "balance is always non-negative" is enforced by the class itself. This is encapsulation's real value: enforcing invariants that would otherwise require trusting every caller to behave correctly. Encapsulation also reduces cognitive load: when reviewing or using a class, you only need to understand its public API (the public methods and properties), not its internal implementation. The internal state and helper methods are invisible noise to the caller.

Code Example

javascript
// Encapsulation with private fields and validated interface
class BankAccount {
  #balance;
  #owner;
  #transactions = [];

  constructor(owner, initialBalance = 0) {
    if (initialBalance < 0) throw new Error('Initial balance cannot be negative');
    this.#owner = owner;
    this.#balance = initialBalance;
  }

  // Public interface: controlled access to private state
  get balance() { return this.#balance; }
  get owner()   { return this.#owner; }

  deposit(amount) {
    if (amount <= 0) throw new Error('Deposit must be positive');
    this.#balance += amount;
    this.#record('deposit', amount);
    return this;  // chainable
  }

  withdraw(amount) {
    if (amount <= 0) throw new Error('Withdrawal must be positive');
    if (amount > this.#balance) throw new Error('Insufficient funds');
    this.#balance -= amount;
    this.#record('withdrawal', amount);
    return this;
  }

  getStatement() {
    return [...this.#transactions]; // return copy, not internal reference
  }

  // Private helper — only accessible inside the class
  #record(type, amount) {
    this.#transactions.push({ type, amount, balance: this.#balance, date: new Date() });
  }
}

const acc = new BankAccount('Alice', 1000);
acc.deposit(500).withdraw(200);
console.log(acc.balance); // 1300
// acc.#balance = -999;  // SyntaxError: private field
// acc.#record('fraud', 0); // SyntaxError

Why It Matters for Engineers

Encapsulation is the difference between a class that's safe to use and one that's a time bomb. When internal state is directly accessible, any part of the codebase can corrupt it — and the bug manifests far from the corruption site, making debugging painful. Private fields with validated setters make bugs manifest immediately at the point of violation with a clear error message. AI-generated code frequently skips encapsulation — everything is public, validation is absent, and invariants are unenforced. Recognizing this pattern and knowing how to add proper encapsulation is a key code review skill.

Related Terms

Class · Abstraction · Inheritance · Immutability

Learn This In Practice

Go deeper with the full module on Beyond Vibe Code.

Programming Foundations → →