DEV Community

Cover image for Encapsulation in OOP: Protecting an Object's State from Unintended Changes
Ashay Tiwari
Ashay Tiwari

Posted on

Encapsulation in OOP: Protecting an Object's State from Unintended Changes

In the previous articles, we discussed why Object-Oriented Programming was introduced and explored different ways of creating objects using factory functions and classes.

Now it's time to look at one of the most important ideas behind OOP: Encapsulation.

Encapsulation

Like every concept in this series, let's start with the problem rather than the definition.


The Real Problem Encapsulation Solves

Imagine you're building a bank account.

Without encapsulation:

class BankAccount {
  balance = 1000;
}

const account = new BankAccount();

account.balance = -50000;
Enter fullscreen mode Exit fullscreen mode

Now your account contains:

Balance = -50000
Enter fullscreen mode Exit fullscreen mode

which may be impossible according to business rules.

The problem isn't that someone changed a variable.

The problem is:

Anybody can put the object into an invalid state.

Encapsulation is NOT About Hiding

Most beginners think:

Encapsulation = private fields

No.

Private fields are just one tool.

The actual goal is:

Protecting the integrity of an object's state.

An object should control how its data changes.


Why This Becomes Dangerous

In small applications, unrestricted access may not seem like a big deal.

As applications grow, however, data starts being accessed from many places.

Imagine:

  • A payment module updating balances.
  • A refund module updating balances.
  • An admin panel updating balances.
  • A reporting service reading balances.

Now suppose an issue appears.

A customer's balance becomes negative.

Where did it happen?

Which part of the application modified it?

Was validation skipped?

Did someone accidentally overwrite a value?

The more places that can directly manipulate data, the harder it becomes to reason about the system.


A Better Design

Instead of:

account.balance = -50000;
Enter fullscreen mode Exit fullscreen mode

force all changes through methods:

class BankAccount {
  private balance = 1000;

  deposit(amount: number) {
    this.balance += amount;
  }

  withdraw(amount: number) {
    if (amount > this.balance) {
      throw new Error('Insufficient funds');
    }

    this.balance -= amount;
  }

  getBalance() {
    return this.balance;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now:

account.withdraw(500);
Enter fullscreen mode Exit fullscreen mode

is allowed.

But:

account.balance=-50000;
Enter fullscreen mode Exit fullscreen mode

is impossible.

The object protects itself.

Think Like a Security Guard

Imagine a company database.

Bad design:

Employee Database
      ↑
Everybody can modify it
Enter fullscreen mode Exit fullscreen mode

Good design:

Employee Database
      ↑
HR Department
      ↑
Employees submit requests
Enter fullscreen mode Exit fullscreen mode

Employees don't directly change records.

They request changes.

The HR department validates them.

Encapsulation works exactly like this.


The Idea Behind Encapsulation

Encapsulation is about controlling access to an object's internal state.

Instead of allowing anyone to modify data directly, the object becomes responsible for managing its own data.

Rather than exposing the balance:

account.balance = 5000;
Enter fullscreen mode Exit fullscreen mode

we expose behaviors:

account.deposit(5000);
Enter fullscreen mode Exit fullscreen mode

or

account.withdraw(5000);
Enter fullscreen mode Exit fullscreen mode

Now every change passes through rules defined by the object itself.

The object protects its own integrity.

This is the real purpose of Encapsulation.

Not hiding data.

Protecting data.


Encapsulation in TypeScript

TypeScript gives us access modifiers.

class User {
  public name: string;

  private password: string;

  protected role: string;
}
Enter fullscreen mode Exit fullscreen mode

public

Accessible everywhere.

user.name
Enter fullscreen mode Exit fullscreen mode

works.

private

Accessible only inside the class.

user.password
Enter fullscreen mode Exit fullscreen mode

❌ Error

protected

Accessible inside:

  • current class
  • derived classes
class User {
  protected role = 'user';
}

class Admin extends User {
  checkRole() {
    console.log(this.role);
  }
}
Enter fullscreen mode Exit fullscreen mode

Works.


Important Reality About TypeScript

Many developers are surprised by this.

TypeScript:

private balance = 1000;
Enter fullscreen mode Exit fullscreen mode

is primarily a compile-time restriction.

After compilation to JavaScript:

this.balance = 1000;
Enter fullscreen mode Exit fullscreen mode

still exists.

TypeScript prevents you from accessing it in code, but it's not true runtime privacy.

Modern JavaScript introduced:

#balance
Enter fullscreen mode Exit fullscreen mode

which is actual runtime privacy.

class Account {
  #balance = 1000;
}
Enter fullscreen mode Exit fullscreen mode

Now:

account.#balance
Enter fullscreen mode Exit fullscreen mode

is impossible.


The Key Takeaway

Encapsulation is often described as hiding data.

A more useful way to think about it is this:

Encapsulation ensures that an object controls how its own state changes.

By doing so, it protects business rules, reduces accidental misuse, and makes software easier to maintain as systems grow.


What's Next?

Encapsulation helps us control access to data.

But another challenge still remains.

Even when objects manage their own state correctly, consumers often need to understand too many implementation details to use them.

In the next article, we'll explore Abstraction and see how hiding unnecessary complexity allows us to build systems that are easier to use and easier to change.


Top comments (0)