Encapsulation
Encapsulation bundles data and the methods that operate on it into one unit and restricts direct access to the internals, exposing a controlled interface instead.
Hide the data.
Encapsulation means keeping an object's data private and only letting the outside world interact with it through methods. Think of a medicine capsule: the contents are wrapped inside, and you use it through its defined shape — you don't poke at the powder directly.
Instead of exposing a raw balance field that anyone can set to a negative number, a
BankAccount hides it and offers deposit() and withdraw() methods
that enforce the rules.
# Problem: balance is public. Callers can break invariants freely. class BankAccount: def __init__(self): self.balance = 0 acct = BankAccount() acct.balance = -999 # nonsense state — no rule can stop this acct.balance += 50 # also skips any logging / validation
# Fix: hide the field (convention: _balance) and expose safe methods. class BankAccount: def __init__(self): self._balance = 0 # "private by convention" in Python def deposit(self, amount): # All writes go through here — enforce rules in one place. if amount <= 0: raise ValueError("deposit must be positive") self._balance += amount def withdraw(self, amount): if amount > self._balance: raise ValueError("insufficient funds") self._balance -= amount @property def balance(self): # read-only view of the hidden state return self._balance
Access modifiers.
Getters/setters control access to fields, letting you validate, compute, log, or later change the internal representation without breaking callers.
Protecting invariants.
- Protecting invariants — the real goal: no external code can put the object into an invalid state.
- Information hiding — hide decisions likely to change (data format, algorithm), not just data.
- Tell, don't ask — put behavior with the data instead of pulling data out to act on it elsewhere.
- Encapsulation across boundaries — modules/services also encapsulate; leaking internal representations couples clients to them.
Interview Questions
Bundling data (fields) and the methods that operate on it into a single unit (a class) and restricting direct access to the internal state, exposing a controlled public interface instead.
It protects an object's integrity (only valid state changes are allowed), hides implementation details so they can change without breaking callers, and makes code easier to understand, maintain, and debug by localizing responsibility for the data.
Keywords that control the visibility of class members: private (class only), protected (class + subclasses), public (everywhere), and package/internal (same module). They're the primary tool for enforcing encapsulation.
Methods that read (getter) and modify (setter) a private field through a controlled interface, allowing validation, computation, or logging rather than exposing the field directly.
A private member is accessible only from within the class that declares it; external code and even subclasses cannot access it directly, which is how you hide internal state.
A car: you drive using the steering wheel, pedals, and gear shift (the public interface) without touching the engine internals. The complexity is bundled and hidden; you interact through a safe, controlled surface.
No. Encapsulation is a design/organization principle for controlling access and protecting invariants within a program; it's not a security mechanism. Access modifiers can often be bypassed via reflection, so encapsulation prevents accidental misuse, not malicious attacks.
Abstraction is about the design — exposing only essential concepts and hiding complexity (the "what"). Encapsulation is about the implementation — bundling data with behavior and restricting access to internals (the "how it's protected"). Abstraction is achieved through interfaces/abstract types; encapsulation through access modifiers. They're complementary: abstraction hides complexity conceptually, encapsulation enforces the hiding.
No — mechanically adding a public getter and setter for every field defeats the purpose, because it exposes the internal representation and lets callers freely mutate state, no better than public fields. Good encapsulation exposes behavior (methods that do meaningful operations and enforce rules), keeps fields as hidden as possible, and adds accessors only when genuinely needed. Prefer "tell, don't ask."
Instead of asking an object for its data and then making decisions/acting on it externally, tell the object what to do and let it use its own data. E.g. rather than if (account.getBalance() >= amt) account.setBalance(...), call account.withdraw(amt). This keeps behavior with the data it depends on, preserving encapsulation and invariants and reducing coupling.
By hiding the internal representation behind a stable interface, you can change how something is stored or computed (e.g. switch from storing a total to computing it, or change a data structure) without touching any calling code. The public contract stays the same while the implementation evolves, localizing the impact of change — a core enabler of maintainability.
An anemic domain model has objects that hold data (just getters/setters) but no behavior, with all logic living in separate "service" classes. It's criticized because it separates data from the behavior that operates on it, undermining encapsulation: invariants aren't protected by the object, logic gets duplicated/scattered, and you effectively have procedural code dressed as OOP. A rich domain model places behavior with its data.
Don't return the raw internal collection (callers could mutate it, breaking invariants). Instead return an unmodifiable/read-only view or a defensive copy, and expose intent-revealing methods (addItem, removeItem) that enforce rules. Similarly, copy collections passed into constructors/setters before storing them so external references can't mutate your state. This keeps the object in control of its own contents.
The Law of Demeter ("don't talk to strangers") says a method should only call methods on itself, its parameters, objects it creates, and its direct fields — not reach through chains like a.getB().getC().doThing(). Such chains ("train wrecks") expose and couple you to the internal structure of other objects, breaking their encapsulation. Following it keeps objects loosely coupled and their internals hidden, often by adding delegating methods.
Not strictly. Python has no access modifiers; a single underscore (_x) is a convention meaning "internal, please don't touch," and a double underscore (__x) triggers name mangling (_ClassName__x) to avoid subclass collisions, but both can still be accessed. Python relies on convention and "we're all consenting adults" rather than compiler enforcement, unlike Java/C++ which enforce visibility at compile time.
They're related but distinct. Encapsulation is the language mechanism of bundling data with methods and restricting access. Information hiding (Parnas) is the design principle of decomposing a system so each module hides a design decision likely to change (a data format, an algorithm, a hardware detail) behind a stable interface, so changes don't ripple. Encapsulation is a tool that helps achieve information hiding, but you can encapsulate poorly (hide the wrong things) — the real skill is choosing which decisions to hide so the system is resilient to change.
By funneling all access to shared state through the object's own methods, encapsulation gives a single place to add synchronization (locks) and enforce atomicity, so external code can't read/write the state unguarded. This enables designing thread-safe objects (monitor pattern) whose invariants hold under concurrency. Conversely, leaking internal mutable state (returning a reference to an inner collection or field) breaks this guarantee, letting other threads mutate state outside the lock. Immutable, fully-encapsulated objects are inherently thread-safe.
In a published API, exposing fields directly makes it impossible to later add validation or change representation without breaking binary/source compatibility, whereas accessor methods preserve the contract while letting the implementation evolve. But over-exposing state (even via accessors) still couples clients to your data shape, so evolving it forces coordinated changes. Best practice is to expose the minimal, behavior-oriented interface (and stable value objects/DTOs) so you retain freedom to change internals; treat the public surface as a long-term commitment and hide everything that might change. Some languages (C#, Kotlin) offer properties so you can start with a field and add logic later without changing call sites.
Yes. Modules/packages encapsulate by exposing only a public API and hiding internal classes (via package-private/internal visibility, module systems like Java's JPMS, or explicit exports). Microservices encapsulate their data and logic behind an API/contract, hiding their database and implementation (each service owns its data; others must go through the interface). Bounded contexts in DDD encapsulate a domain model. The same principle scales: expose a stable contract, hide the changeable internals, so consumers don't couple to implementation details at any level.
Over-encapsulation can add boilerplate (needless accessors/wrappers), extra indirection that hurts performance in hot paths, and difficulty testing internals (leading some to expose things "for testing" — better to test through the public interface or use package-private access). It can also make simple data structures unnecessarily heavyweight. And encapsulation doesn't prevent determined access (reflection). The goal is judicious hiding of things that change or must be protected — not hiding everything reflexively. Value objects and DTOs are intentionally low on encapsulation because their data is their contract.
Incrementally, behind tests. First make fields private and add temporary accessors to compile, then find call sites that mutate state and replace scattered logic with intent-revealing methods on the class (moving behavior in — "tell, don't ask"), consolidating rules and invariants there. Introduce validation in one place, replace setters with meaningful operations, and remove accessors that are no longer needed. Protect collections with copies/views. Consider making it immutable if feasible. The end state: state is private, all changes go through methods that enforce invariants, and the class exposes behavior rather than raw data. Do it in small, verifiable steps to avoid breaking callers.
Encapsulation hides internal details behind a stable interface, which is what lets a class be open for extension but closed for modification: clients depend on the public contract, so you can change or extend internals without forcing them to change. For testability, well-encapsulated classes with clear public interfaces are tested through that interface (black-box), which keeps tests robust to refactoring; if you find yourself needing to reach into privates to test, it often signals a design issue (too much hidden behavior or a missing collaborator to inject). Good encapsulation plus dependency injection makes units easy to isolate and verify.
It makes a member accessible within its own class and its subclasses (and, in some languages, the same package), but not to unrelated external code. It's used to share internals with derived classes while still hiding them from the outside world.
No — private members are visible only within the declaring class, not to subclasses. To let subclasses use inherited state, the parent must expose it as protected or via protected/public accessor methods.
A field is a raw variable storing data. A property (C#, Kotlin, Python @property) looks like a field to callers but is backed by getter/setter logic, so you can add validation or computation without changing the call site. Properties give you the syntactic convenience of fields with the encapsulation of accessors, so you can start simple and evolve behavior later.
A property that exposes a getter but no setter, so callers can read the value but not change it directly. Use it to publish derived or invariant state (e.g. a computed fullName, or an ID set once at construction) while keeping the object in control of when and how it changes — reads are safe, mutations only happen through meaningful methods.
By hiding internal representation behind a small, stable interface, callers depend only on that interface, not on how the object stores or computes things. So changes to internals don't ripple to clients, and clients can't create hidden dependencies on your data layout. This narrows the "surface area" of interaction, which is exactly what low coupling means.
Feature envy is a code smell where a method is more interested in another object's data than its own — it repeatedly calls getters on another object to make decisions. It signals misplaced behavior and broken encapsulation: the logic belongs with the data it uses. The fix (move method) relocates the behavior into the class that owns the data, restoring "tell, don't ask" and letting that class hide its internals. It's a practical diagnostic for where encapsulation has leaked.
Avoid raw public global variables and scattered singletons, which are un-encapsulated shared mutable state (hidden coupling, hard to test). Instead wrap configuration in an immutable object with a typed interface, load it once, and inject it into the components that need it (dependency injection) rather than having them reach out to a global. This hides the source/format, allows validation and defaults in one place, makes dependencies explicit, and lets tests supply alternative configs. If a singleton is unavoidable, hide it behind an interface so consumers depend on the abstraction, not the global.
Even without classes, FP encapsulates via closures (functions capturing private variables that outsiders can't touch, exposing only returned functions), modules (exporting a public API while keeping helpers private), and abstract/opaque data types (exposing constructor and operation functions but hiding the representation, e.g. a Map ADT). Immutability further strengthens it: state can't be mutated behind your back at all. So the principle — hide representation, expose a controlled interface, protect invariants — is language-paradigm-independent; only the mechanism (closures/modules vs classes/access modifiers) differs.