Low-Level Design Tutorial 0/42 lessons ~6 min read Lesson 6

    Encapsulation

    Encapsulation hides internal representation and exposes a controlled interface.

    Course progress0%
    Focus
    9 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Encapsulation hides internal representation and exposes a controlled interface. Callers depend on what an object does, not how it stores data — the foundation of maintainable LLD.

    In interviews, broken encapsulation shows up as public collections mutated from anywhere, or balance fields updated without validation. Reviewers infer production bugs: double spending, invalid state transitions, race conditions.

    Strong encapsulation pairs with small public APIs — often the difference between a extensible design and a fragile one.

    Understanding the topic

    Key concepts

    • Hide fields; expose behavior through methods.
    • Invariant protection: only the owning class changes critical state.
    • Defensive copies when returning mutable internals (lists, dates).
    • Package-private for collaboration within a module; public for stable API.
    • Encapsulation enables refactoring internal structure without client changes.
    • Thread-safe encapsulation: synchronize or confine mutations to one thread.

    Step-by-step explanation

    1. Make fields private (or record components already immutable).
    2. Route state changes through methods that validate preconditions.
    3. Return unmodifiable views of collections (List.copyOf, Collections.unmodifiableList).
    4. Avoid leaking this during construction if partially initialized.
    5. Use builder pattern only when construction complexity warrants it.
    6. Document which methods are thread-safe.

    Informative example

    Bank account with encapsulated balance — no direct field access:

    java
    public final class BankAccount {
    private final String accountId;
    private long balanceCents;
    public BankAccount(String accountId, long openingBalanceCents) {
    this.accountId = Objects.requireNonNull(accountId);
    if (openingBalanceCents < 0) throw new IllegalArgumentException("negative opening balance");
    this.balanceCents = openingBalanceCents;
    }
    public synchronized void deposit(long cents) {
    if (cents <= 0) throw new IllegalArgumentException("deposit must be positive");
    balanceCents += cents;
    }
    public synchronized boolean withdraw(long cents) {
    if (cents <= 0 || cents > balanceCents) return false;
    balanceCents -= cents;
    return true;
    }
    public synchronized long balanceCents() { return balanceCents; }
    }

    synchronized methods encapsulate concurrency policy inside the account — callers cannot forget to lock.

    Real-world use

    Real-world applications

    • Protecting invariants in ATM, wallet, and inventory classes.
    • Publishing stable module APIs in large codebases.
    • Preventing unauthorized state jumps in state machines.

    Best practices

    • Never expose mutable collections directly.
    • Validate at system boundaries (constructors, command handlers).
    • Prefer immutability for value types — encapsulation by default.
    • Keep getters minimal; favor intention-revealing methods (withdraw vs setBalance).
    • Use records for transparent immutable values when no behavior needed.
    • Log state transitions inside the class, not from outside.

    Common mistakes

    • public List seats — external code removes elements illegally.
    • Setter-only models with no validation.
    • Returning internal array references that callers mutate.
    • Breaking encapsulation with instanceof chains across packages.

    Advanced interview questions

    Q1BeginnerWhat is encapsulation?
    Bundling data with methods that control access and preserve invariants.
    Q2BeginnerWhy not make all fields public?
    External code can violate rules and tie itself to internal representation details.
    Q3IntermediateHow do you encapsulate a collection?
    Store privately; expose read-only views or domain-specific add/remove methods.
    Q4IntermediateDoes encapsulation conflict with testing?
    No — test through public API; use package-private or test doubles when needed.
    Q5AdvancedHow would you encapsulate multi-field updates atomically?
    Single method on the aggregate root (transfer between accounts) with internal locking.

    Summary

    Hide data; expose controlled behavior. Validate and synchronize inside the class boundary. Defensive copies prevent external mutation. Small public APIs reduce coupling. Encapsulation enables safe refactoring.

    Ready to mark this lesson complete?Track your journey across the entire course.