Objects & ClassesIntermediate6 min14 / 23

Encapsulation

Hide an object's internals behind a clean interface using private #fields and getters/setters, so state can't be corrupted from outside.

Encapsulation means bundling data with the methods that operate on it, and hiding the internal details. Callers use a small, safe interface; they can't reach in and put the object into a broken state. Modern JS supports true privacy with # fields.

private #fields
class BankAccount {
  #balance = 0;               // truly private — # marks it

  deposit(amount) {
    if (amount <= 0) throw new Error('must be positive');
    this.#balance += amount;
  }
  get balance() {             // controlled read access
    return this.#balance;
  }
}

const acc = new BankAccount();
acc.deposit(100);
console.log(acc.balance);   // 100
// console.log(acc.#balance) // SyntaxError — private outside the class

#balance can only be touched by code inside the class. Outsiders go through deposit() (which validates) and the balance getter (read-only). There's no way to set a negative balance from outside.

Note

Before # fields: the _underscore convention

Older code marks 'private' fields with a leading underscore (this._balance) — but that's only a convention; nothing stops access. The # prefix is real, enforced privacy. Closures are another classic way to hide state (a variable captured in a factory function, as in the Closures lesson).

Quick check

What happens if you try to read `acc.#balance` from outside the class?

Key takeaways

  • Encapsulation hides internals behind a small, safe public interface.
  • `#field` declares a truly private field — inaccessible outside the class.
  • Expose controlled access with methods and get/set, validating on the way in.
  • The old `_name` underscore is convention only; closures are another way to keep state private.
Practice challenges
Test yourself · earn XP
0/3
Predict the output#1

What does this print?

predict-output
class Counter {
  #n = 0;
  bump() { this.#n++; }
  get value() { return this.#n; }
}
const c = new Counter();
c.bump(); c.bump(); c.bump();
console.log(c.value);
Fix the bug#2

Reading `acc.#balance` outside the class is a SyntaxError. What's the right way to expose it?

fix-bug
class Acc { #balance = 100; }
console.log(new Acc().#balance);
Fill in the blank#3

Complete the declaration of a truly private field.

class Vault {
  secret = 42;
}
Your turn
Practice exercise

Give a Counter class a private #count starting at 0, an increment() method, and a read-only value getter. Calling increment twice then reading value should give 2.

Try it live — edit the code and hit Run to see the output:

solution.js · editable