The idea
What it is
Think of a medicine capsule. The powder inside is sealed — you don't pour it out and re-measure it yourself. You take the capsule as a whole, and the casing makes sure you get exactly the right dose. Encapsulation is that idea in code: an object wraps its data inside a protective shell and only lets you interact with it through a few trusted methods.
Without it, any code anywhere could reach into an object and scramble its data — set a bank balance to -9999, give an order a quantity of zero, put a date in an impossible state. With encapsulation, the object becomes the guardian of its own data. Want to change something? You go through a method, and that method gets to say yes or no.
The one sentence to remember
Make the data private, expose methods that guard it. The object protects its own rules so no outside code can put it in a broken state.
Mechanics
How it works
Private vs. public
Every member of a class is either public — anyone can see and touch it — or private — only the object's own methods can. Encapsulation is the habit of keeping data private and making a small, deliberate set of methods public. The public methods are the object's front door; the private fields are the locked room behind it.
Picture a vending machine. There's a locked cash box inside (private). You can't open it and take the money. What you can do is press buttons and insert coins (public methods). The machine decides whether to dispense a snack and how much change to return. The rules live with the machine, not with whoever walks up to it.
Access modifiers
Languages give you keywords to mark this boundary. In TypeScript it's private (or a #name field that's truly hidden at runtime). In Java and C++ it's private. Python has no hard private, so the convention is a leading underscore like _balance, often paired with a property to control access. Same idea everywhere: a label that says this is internal, don't touch it from outside.
Getters and setters — done right
A getter reads a private field; a setter changes it. The point of routing through them is not to mindlessly mirror the field — it's to add a checkpoint. A good setter validates:
- Validate before you write. A
deposit(amount)rejects a negative amount; asetTemperature(c)rejects anything below absolute zero. The method refuses bad input instead of storing it. - Expose read-only when that's all you need. Often you want a getter and no setter at all — outsiders can look at the balance but can only change it by depositing or withdrawing.
- Don't just pass the field through. A setter that does
this._balance = valuewith no check gives away everything privacy bought you. If a field has no rules, maybe it doesn't need a setter.
Invariants: the rules the object guards
An invariant is a fact that must always be true about an object — for a bank account, balance >= 0. Encapsulation is what lets the object promise that invariant. Because the only way in is through deposit and withdraw, and those methods check the rule, the balance can never go negative. The object enforces its own correctness.
class BankAccount {
// private — the front door is the methods below, not this field
#balance = 0;
deposit(amount: number) {
if (amount <= 0) throw new Error("amount must be positive");
this.#balance += amount; // guarded write
}
withdraw(amount: number) {
if (amount > this.#balance) throw new Error("insufficient funds");
this.#balance -= amount; // invariant balance >= 0 holds
}
get balance() { return this.#balance; } // read-only view, no setter
}
const a = new BankAccount();
a.deposit(100);
// a.#balance = -5 ← won't even compile; the field is unreachableWhy data and its guard methods belong together
The validation rules and the data they protect are two halves of one thing. If they're split apart — data in one place, the checks scattered across the codebase — sooner or later someone changes the data without running the checks. Keeping balance and the deposit/withdraw that guard it in the same class means there's exactly one trusted path in, and the rules can't be skipped.
Encapsulation vs. abstraction
They sound alike but solve different problems. Encapsulation hides and protects data (private fields behind guard methods). Abstraction hides complexity (a simple interface over messy internals). Encapsulation is about who can touch what; abstraction is about what you have to know.
Interactive prototype
See it. Build it. Break it.
A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.
About this simulation
One BankAccount drawn as a capsule with a shielded interior holding the private balance. Try the left column — account.balance = X — and the shield flashes red: outside code can't touch a private field. Use the right column — deposit and withdraw — and watch validation run before the balance changes. The balance >= 0 invariant never breaks.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Try to break in directly
Type a number into the left column and press account.balance = X. The shield flashes red and the log says the field is private — outside code can't touch it. The balance doesn't budge. That's the privacy boundary doing its job.
Go through the methods
Use the right column to deposit(50), then withdraw(30). Valid operations flash green and the shielded balance updates. This is the front door — the only sanctioned way to change the data.
Watch validation reject bad input
Try deposit(-20) or withdraw more than the current balance. The method refuses and logs why, and the balance >= 0 invariant badge stays satisfied. The object guards its own rules even when you ask it to misbehave.
In practice
When to use it — and what trips people up
When to make things private
- When a field has rules — a balance that can't go negative, a percentage that must stay 0–100, a status that only changes in a set order.
- When you want to change the internals later without breaking callers — hide the field now, and you're free to swap how it's stored.
- When data and the logic that protects it belong together — keep them in one class so the checks can't be skipped.
Make fields private by default
Start every field as private and only open it up when you have a concrete reason. It's far easier to expose something later than to claw back access once code all over the codebase depends on a public field.
What it gives you
- The object guards its own invariants — bad states become impossible, not just discouraged.
- One trusted path in means validation can't be bypassed or forgotten.
- You can change the internal representation freely without breaking any caller.
- Bugs are easier to find — if the balance is wrong, the cause is in one small set of methods.
Common mistakes
- Anemic getters/setters that just expose every field — that's a public field wearing a disguise, with none of the protection.
- Leaking mutable internals — returning the actual list or array lets callers mutate your private state behind your back.
- Over-encapsulating — wrapping a plain value object with no rules in layers of ceremony adds noise for nothing.
- Treating
privateas security — it stops accidents and enforces design, but it isn't a defense against a determined attacker.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// balance is private; the only way to change it is through guarded methods.
class BankAccount {
#balance: number;
constructor(opening = 0) {
if (opening < 0) throw new Error("opening balance can't be negative");
this.#balance = opening;
}
deposit(amount: number) {
if (amount <= 0) throw new Error("deposit must be positive");
this.#balance += amount;
}
withdraw(amount: number) {
if (amount <= 0) throw new Error("withdraw must be positive");
if (amount > this.#balance) throw new Error("insufficient funds");
this.#balance -= amount;
}
// read-only view — no setter, so outsiders can look but not poke
get balance() {
return this.#balance;
}
}
const acct = new BankAccount(100);
acct.deposit(50); // → 150
acct.withdraw(30); // → 120
// acct.#balance = -5 ← syntax error: '#balance' is not accessible
// acct.balance = -5 ← error: no setter; 'balance' is read-onlyReferences & further reading
5 sources- Articleen.wikipedia.org
Wikipedia — Encapsulation (computer programming)
A broad overview of the concept and how it relates to information hiding across languages.
- Docsdeveloper.mozilla.org
MDN — Private properties
How JavaScript's
#nameprivate fields work — truly hidden, not just convention. - Docsdocs.oracle.com
Oracle — Controlling Access to Members of a Class
The definitive table of Java access modifiers: private, package-private, protected, public.
- Docsdocs.python.org
Python docs — property
How to add validating getters and setters in Python without changing the public attribute syntax.
- Articlerefactoring.guru
Refactoring.guru — Encapsulate Field
A step-by-step refactoring that turns a public field into a private one behind accessors.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Nothing is scored or saved.
question 01 / 04
What is the core purpose of encapsulation?
question 02 / 04
A BankAccount has a private balance and a withdraw(amount) method that rejects amounts greater than the balance. Why keep balance private?
question 03 / 04
Which getter/setter pair actually adds value over a plain public field?
question 04 / 04
How do encapsulation and abstraction differ?
0/4 answered