How do access modifiers shape a class's public API, and what is the practical cost of choosing a wider level than necessary?
answer
- public/protected = a promise you must keep
- Widening access is cheap; narrowing it later breaks callers
- Start private, widen one step only when forced
- protected bakes inheritance into the contract (fragile base class)
- Never expose mutable fields — expose behavior
basics
~20 sWhatever you mark public or protected becomes a promise others can depend on. Wider access means more callers can rely on it, so changing or removing it later can break them. Default to private and widen only when needed.
solid answer
~50 sAccess modifiers define the contract surface of a class: everything public or protected is part of the API that external code (or subclasses) may bind to, and once code in the wild depends on it you can't change its signature or remove it without breaking those callers. So the cost of over-exposing is paid later, in lost freedom to refactor and in accidental coupling. The discipline is least privilege: start everything private, widen one step only when a concrete collaborator genuinely needs it — package-private for same-package helpers, protected only for deliberate subclass extension points, public only for the intended client API. protected is especially expensive because it also bakes inheritance into your contract: subclasses can rely on the member's existence and semantics, constraining how you evolve the class. Narrow access keeps your changeable internals genuinely changeable, which is the whole economic argument for encapsulation.
go deeper
Recognize that public/private affect who can use a member; state that private is the safer default.
Apply least-privilege consistently and explain that public members become commitments that constrain future changes.
Articulate the cost asymmetry (widening cheap, narrowing breaking), why protected is the most expensive level, and design a minimal public surface deliberately.
Reason about API/binary compatibility across modules and teams, inheritance contracts and fragile base classes, and governance of public surface as a long-lived liability.
## The core idea: access level = contract scope An **access modifier** controls who may use a member, but its deeper meaning is *who is allowed to depend on it*. Anything you make **public** or **protected** becomes part of your class's **API** — the set of things outside code can bind to and therefore expects to keep working. ("API" = application programming interface: the promises a unit makes to its callers.) The key asymmetry: **widening access later is cheap; narrowing it later is expensive.** Adding `public` to something previously `private` breaks nobody. *Removing* `public` (or changing a public method's signature) can break every caller that came to depend on it — and once your class is used by other packages, modules, or teams, you usually don't even know who they all are. So an access level is effectively a **one-way promise**: the moment something is public and used, you've committed to maintaining it (this is the basis of *binary/source compatibility*). ## The cost of over-exposing, concretely 1. **Lost refactor freedom.** A `private` field can be renamed, retyped, or deleted at will. A `public` field cannot — external code reads it directly, so you can't even swap it for a computed getter without breaking callers. 2. **Accidental coupling.** Every exposed member is an invitation; other code *will* reach for it, creating dependencies you didn't intend and can't see. 3. **Frozen invariants.** A public mutable field lets outsiders put the object into illegal states; you've given up the ability to enforce validation in one place. ## Why `protected` is the most expensive of all `protected` exposes a member to **subclasses across packages**, which means subclasses may **override** and **depend on** it. That folds inheritance into your contract: you've promised not just *what* the member does but that it *exists as an extension point* with stable semantics — the basis of the **fragile base class** problem, where changing a base class silently breaks subclasses. Treat every `protected` member as a designed, documented extension point, not a casual loosening of `private`. ## The discipline: least privilege The practical rule (Effective Java, Item: "minimize accessibility"): - Make **every** member as **inaccessible as possible**. Start at `private`. - Widen **one step** only when a real collaborator forces it: - **package-private** for a helper another class in the same package needs; - **`protected`** only for a *deliberate, documented* subclass extension point; - **`public`** only for the intended client-facing API. - Never expose **mutable** fields publicly; expose behavior (methods) instead of state. ```java public class PriceList { private final Map<String,Long> prices = new HashMap<>(); // hidden internals — free to change public long priceOf(String sku) { // the deliberate API surface return prices.getOrDefault(sku, 0L); } } ``` Here the `Map` representation can be swapped (to a cache, a DB call, etc.) without touching any caller, precisely because it's `private`. Had `prices` been `public`, that freedom would be gone. ## Bottom line Access modifiers are an **economic** tool: every step toward `public`/`protected` trades short-term convenience for long-term maintenance liability. Spend that openness deliberately, on a small, intended surface, and keep everything else hidden.
- Why is protected considered more costly to expose than public for evolution?Because it commits the member as an inheritance extension point: subclasses override and depend on it, so changes can silently break them (the fragile base class problem) — a constraint public-but-final members don't impose.
- What's the default access level you should reach for, and why?private. It maximizes your freedom to change internals, since no outside code can depend on a private member; you widen only when a concrete collaborator requires it.
saying these in an interview costs you the question
- Marking everything public 'to be safe' — it maximizes future breakage
- Treating protected as a casual loosening rather than a designed extension point
- Exposing mutable fields publicly and losing invariant enforcement
- Assuming you can always remove a public member later without consequence