Beginner10 min readObject-Oriented Foundationslive prototype

Access Modifiers

public / protected / package-private / private control WHO can see a member — the dial that makes encapsulation enforceable.

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-private with 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 protected and public, but not private and not package-private.
  • Same package (not a subclass) — sees package-private, protected, and public, but not private.
  • Outside world (different package, not a subclass) — sees public only. 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.

convention.py
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.

try 01

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.

try 02

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.

try 03

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 enforceableprivate turns '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.
  • protected lets 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 protected

References & further reading

4 sources

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