The idea
What it is
Encapsulation says keep your data hidden and expose a few trusted methods. But hidden from whom? That's the question access modifiers answer. They're the dial on each member of a class that decides who is allowed to see and touch it: the class itself, its subclasses, other code in the same package, or absolutely anyone.
Think of a building with rooms at different clearance levels. The lobby is public — anyone walks in. Some rooms are for staff of the same department (package-private). A few are for staff and trainees who report up the chain (protected). And there's a private office that only you can enter (private). Same building, four levels of access, each door labelled with who may open it.
The one sentence to remember
A member's access modifier names its audience — who can reach it. private = just this class. package-private = this package. protected = this package plus subclasses. public = everyone.
Mechanics
How it works
The four levels, from tightest to widest
Picture four concentric rings of access. Each level lets in everyone from the tighter levels plus one more group:
- `private` — visible only inside the same class. Not even a subclass can see it. This is the default home for fields.
- package-private (Java's default, written by adding no modifier) — visible to the same class and any other class in the same package. There's no keyword; the absence of one is the signal.
- `protected` — visible to the same package and to subclasses, even subclasses in another package. It's
package-privatewith an extra door for children. - `public` — visible to anyone, anywhere. This is the deliberate, advertised front door of your class.
Who can access what
The clearest way to hold this is a table of caller context versus level. Reading across a row tells you exactly who's allowed in:
- Same class — sees everything:
private, package-private,protected,public. - A subclass (in another package) — sees
protectedandpublic, but notprivateand not package-private. - Same package (not a subclass) — sees package-private,
protected, andpublic, but notprivate. - Outside world (different package, not a subclass) — sees
publiconly. Everything else is locked.
Languages draw the lines differently
Not every language ships all four levels. Java has the full set: private, package-private (no keyword), protected, and public. TypeScript and C++ have public, protected, and private — there is no package level, because neither language has Java-style packages. Python takes a different route entirely: it has no enforced access at all, only convention.
class Account:
region = "EU" # public by convention
_audit_log = [] # one underscore: "internal, please don't touch"
__balance = 0 # two underscores: name-mangled to _Account__balance
# acct._audit_log works, but the _ asks you not to.
# acct.__balance raises AttributeError — Python rewrote the name.In Python a single leading underscore like _audit_log is a polite don't touch this — nothing stops you, it's a gentleman's agreement. A double underscore like __balance triggers name-mangling: Python secretly renames it to _Account__balance, which makes accidental access from outside fail. It's still not true privacy, but it raises the bar.
The golden rule
Make every member as private as possible, then widen only when a real caller needs it. It's trivial to open access later; it's painful to take it back once code across the codebase depends on it.
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 Account class with members tagged at different visibility levels. Pick a caller context at the top — Inside the same class, A subclass, Same package, or Outside world — and every member row instantly recolors green (you can reach it) or red with a lock (you can't). Click a blocked member to log exactly why it's off-limits.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Start locked down
On load the caller context is Outside world — only the public members (id, getBalance()) glow green. Everything else shows a red lock. This is what a stranger to your class sees: the advertised front door and nothing more.
Switch context and watch the matrix recolor
Click through Inside the same class → A subclass → Same package. Each switch instantly repaints the rows. Notice auditLog (protected) turns green for a subclass, while region (package-private) stays red for it — and the explanation line spells out why.
Click a blocked member to learn why
While in a restricted context, click any red row — say balance from a subclass. The log prints ✗ balance is private — not visible from A subclass. It's the access rule, stated in plain words for the exact case you tried.
In practice
When to use it — and what trips people up
How to pick a level
Default everything to the tightest level that still works, and widen one notch at a time only when a real caller forces it. Keep fields private and let methods guard them. Reach for protected only when a subclass genuinely needs to build on a member. Use package-private (in Java) to share helpers within a module without advertising them to the whole world. Make a member public only when it's part of the deliberate, supported interface you're willing to keep stable.
Public is a promise
Every public member is a commitment: callers will depend on it, so changing or removing it later breaks them. Treat your public surface as a contract you have to honor, and keep it as small as you can.
What it gives you
- Visibility control is what makes encapsulation enforceable —
privateturns 'please don't touch' into 'you can't'. - A small public surface is a small contract: less to keep stable, more freedom to change internals.
protectedlets subclasses extend a class on purpose, without exposing internals to the whole world.- Package-private (in Java) shares helpers within a module while keeping them invisible to outside code.
Common mistakes
- Making everything
public— the most common mistake; it throws away every benefit and locks you into the current layout. - Overusing
protected— it quietly commits internals to every future subclass, an interface that's hard to change. - Leaking internals through a public getter that returns the actual private list or array, so callers mutate your state behind your back.
- Confusing Python's convention with enforcement — a leading underscore is a request, not a wall; treating it as security invites surprises.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// TypeScript has public / protected / private — NO package level.
class Account {
public id: string; // anyone, anywhere can read this
protected auditLog: string[]; // this class + any subclass can touch it
private balance: number; // only methods inside Account can touch it
constructor(id: string, opening: number) {
this.id = id;
this.auditLog = [];
this.balance = opening;
}
public getBalance(): number { // public method — the sanctioned read path
return this.balance;
}
}
const a = new Account("AC-1", 100);
a.id; // ✓ public — fine
a.getBalance(); // ✓ public — fine
// a.balance; // ✗ Property 'balance' is private
// a.auditLog; // ✗ Property 'auditLog' is protectedReferences & further reading
4 sources- Docsdocs.oracle.com
Oracle — Controlling Access to Members of a Class
The definitive Java table of private, package-private, protected, and public — who can access what.
- Docstypescriptlang.org
TypeScript Handbook — Member Visibility
How public, protected, and private work on TypeScript class members (note: no package level).
- Docsdeveloper.mozilla.org
MDN — Private properties
JavaScript's
#nameprivate fields — truly hidden at runtime, the closest the language gets to enforced privacy. - Articleen.wikipedia.org
Wikipedia — Access modifiers
A cross-language overview of how different languages model visibility and access control.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Nothing is scored or saved.
question 01 / 04
A field is marked private in a class. Which caller can read it directly?
question 02 / 04
In Java, what does a member with NO access modifier (e.g. String region;) mean?
question 03 / 04
From a subclass in a different package, which member of its parent can it access?
question 04 / 04
Which statement about Python's access control is correct?
0/4 answered