OBJECT-ORIENTED FOUNDATIONS / OBJECT DESIGN BRIEF

Encapsulation

Encapsulation means hiding an object's internal state and exposing only a controlled set of operations.

BeginnerPhase 01 / Topic 2 of 7ResponsibilitiesCollaborationsExtensibility
01

Overview

Encapsulation means hiding an object's internal state and exposing only a controlled set of operations. Other code cannot reach in and set fields to invalid values; it must go through methods that enforce the rules. The object guarantees its own invariants.

Encapsulation is what makes change safe. If an Order stores items in a List today and a Map tomorrow, callers never notice because they only use addItem() and total(). Private fields, returning copies or read-only views instead of internal collections, and small public APIs are the everyday tools.

A car dashboard

You control the car with a steering wheel, pedals, and buttons. You cannot directly change the fuel injection timing. The engineers can redesign the engine without changing how you drive.

02

When to use it

  • Any class with rules about its state (balance never negative, status transitions).
  • Internal representation may change later.
  • Objects shared by many callers who should not break them.
03

Where it shows up in interviews

Protecting invariants

Recognize it when: state must stay valid (inventory, balance, status).

  • Design a vending machine
  • Design a bank account
Leaky collections

Recognize it when: getters return internal lists.

  • Design an order with line items
  • Design a playlist
04

Where it is used in real software

Java collections

Collections.unmodifiableList and List.copyOf let classes expose data without allowing external mutation.

Public APIs and SDKs

Libraries expose small interfaces and keep implementation packages private so they can change internals between versions.

React state

Components hide state and expose it through props and callbacks rather than letting parents mutate it.

05

Key terms

Access modifiers
private, protected, package, public control visibility.
Invariant
A condition that must always hold for the object.
Defensive copy
Returning or storing a copy so outside code cannot mutate internals.
Information hiding
Hide design decisions likely to change.
06

How it works, step by step

  1. 1
    Make fields private

    Default to the smallest visibility.

  2. 2
    Expose intention-revealing methods

    addItem(), cancel(), not setStatus().

  3. 3
    Validate in every mutator

    Reject invalid transitions.

  4. 4
    Never leak mutable internals

    Return copies or read-only views.

  5. 5
    Keep the public API small

    Every public member is a promise.

07

Leaky vs encapsulated order

An order must never have a negative quantity or be edited after it ships

Step 1 / 4
ActionLeaky (public list)Encapsulated
order.items.push({qty: -3})Accepted: corrupt orderNot possible: no direct access
order.addItem(sku, -3)-Rejected: quantity must be positive
Edit after shippingAnyone can change statusaddItem throws: order already shipped
Switch List to Map internallyAll callers breakNo caller changes

NOWAction: order.items.push({qty: -3}) | Leaky (public list): Accepted: corrupt order | Encapsulated: Not possible: no direct access

Encapsulation turns 'please don't do that' comments into guarantees enforced by code.

08

Implementation

type Line = { sku: string; qty: number; priceCents: number }; class Order {  #lines = new Map<string, Line>();  #status: "open" | "shipped" = "open";   addItem(sku: string, qty: number, priceCents: number) {    if (this.#status !== "open") throw new Error("Order already shipped");    if (qty <= 0) throw new Error("Quantity must be positive");    const existing = this.#lines.get(sku);    this.#lines.set(sku, { sku, qty: (existing?.qty ?? 0) + qty, priceCents });  }   ship() {    if (this.#lines.size === 0) throw new Error("Cannot ship an empty order");    this.#status = "shipped";  }   lines(): readonly Line[] {    return [...this.#lines.values()].map((l) => ({ ...l })); // copies, not internals  }   total() {    return [...this.#lines.values()].reduce((s, l) => s + l.qty * l.priceCents, 0);  }}
09

Complexity and performance

Defensive copyO(n)

Use read-only views for large collections.

ValidationO(1) typically

Per mutation.

10

Trade-offs

Safety vs convenience

Strict APIs prevent misuse but require more methods than exposing fields.

Copies vs performance

Defensive copies cost allocations; immutable structures or views avoid them.

11

Variants and related techniques

Module-level encapsulation

Export only public functions from a module or package.

Immutability

The strongest encapsulation: state never changes after construction.

12

Common mistakes

  • Getters and setters for every field.

    Fix: Setters bypass rules; expose meaningful operations instead.

  • Returning the internal list.

    Fix: Return an unmodifiable copy or view.

  • Validating only in the UI or controller.

    Fix: The object must enforce its own invariants.

13

Interview questions

Why are setters often a sign of weak encapsulation?

A generic setter lets any caller put the object into any state, bypassing business rules. Methods named after actions (ship, cancel) can validate transitions.

How do you expose a collection safely?

Return an unmodifiable copy or view, or expose iteration and query methods, and provide add/remove methods that validate changes.

14

Practice problems

ProblemDifficultyWhat it trains
Encapsulate a Playlist with add, remove, reorderEasyCollection safety.
Encapsulate a Ticket with status transitionsMediumInvariants.