Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
LoginSign Up
menu
  1. Home
  2. /Web Development
  3. /Typescript
  4. /Access Modifiers
Your Progress0%
0 of 30 completed

TypeScript Topics

Getting Started

  • What is TypeScript?
  • TypeScript vs JavaScript
  • Setting Up TypeScript
  • Your First TypeScript Program

Basic Types

  • Primitive Types
  • Arrays and Tuples
  • Objects and Type Aliases
  • Enums
  • Any, Unknown, and Never

Functions & Interfaces

  • Function Types and Signatures
  • Interfaces
  • Optional and Default Parameters
  • Function Overloading

Advanced Types

  • Union and Intersection Types
  • Type Guards and Narrowing
  • Literal Types
  • Type Assertions and Casting

Generics

  • Introduction to Generics
  • Generic Constraints
  • Utility Types

Classes & OOP

  • Classes and Constructors
  • Access Modifiers
  • Inheritance and Polymorphism
  • Abstract Classes and Interfaces

Practical TypeScript

  • Working with Modules
  • TypeScript with React
  • Async/Await and Promises
  • Working with APIs and JSON
  • Type Declarations and DefinitelyTyped
  • TypeScript Best Practices

Access Modifiers

Controlling visibility with public, private, and protected

Access modifiers control the visibility and accessibility of class members (properties and methods). TypeScript provides three access modifiers: public (accessible everywhere), private (accessible only within the class), and protected (accessible within the class and subclasses). These modifiers are essential for encapsulation and data hiding. Let's master access control!

Public Modifier

public members are accessible from anywhere - inside the class, outside the class, and in subclasses. This is the default if no modifier is specified.

public-modifier.ts
class User {
  // Explicitly public
  public name: string;
  public email: string;
  
  // No modifier = public by default
  age: number;
  
  constructor(name: string, email: string, age: number) {
    this.name = name;
    this.email = email;
    this.age = age;
  }
  
  // Public method
  public displayInfo(): void {
    console.log(`Name: ${this.name}`);
    console.log(`Email: ${this.email}`);
    console.log(`Age: ${this.age}`);
  }
}

const user = new User("Yusuf Ibrahim", "yusuf@example.com", 25);

// ✅ Can access public properties
console.log(user.name);   // "Yusuf Ibrahim"
console.log(user.email);  // "yusuf@example.com"
console.log(user.age);    // 25

// ✅ Can modify public properties
user.name = "Yusuf A. Ibrahim";
console.log(user.name);  // "Yusuf A. Ibrahim"

// ✅ Can call public methods
user.displayInfo();

// Parameter properties with public
class Product {
  constructor(
    public id: number,
    public name: string,
    public price: number
  ) {}
  
  public getFormattedPrice(): string {
    return `₦${this.price.toLocaleString()}`;
  }
}

const laptop = new Product(1, "Laptop", 450000);
console.log(laptop.name);   // "Laptop"
console.log(laptop.price);  // 450000
console.log(laptop.getFormattedPrice());  // "₦450,000"

💡 Public is the Default

If you don't specify an access modifier, members are public by default. However, being explicit with public improves code readability and makes your intentions clear.

Private Modifier

private members are only accessible within the class where they're defined. They cannot be accessed from outside the class or from subclasses.

private-modifier.ts
class BankAccount {
  public accountNumber: string;
  private balance: number;  // Only accessible inside this class
  private pin: string;
  
  constructor(accountNumber: string, initialBalance: number, pin: string) {
    this.accountNumber = accountNumber;
    this.balance = initialBalance;
    this.pin = pin;
  }
  
  // Public method to access private data
  public getBalance(): number {
    return this.balance;
  }
  
  // Public method with validation
  public deposit(amount: number): void {
    if (amount <= 0) {
      throw new Error("Deposit amount must be positive");
    }
    
    this.balance += amount;
    console.log(`Deposited ₦${amount}. New balance: ₦${this.balance}`);
  }
  
  // Public method with PIN verification
  public withdraw(amount: number, inputPin: string): boolean {
    if (!this.verifyPin(inputPin)) {
      console.log("Invalid PIN");
      return false;
    }
    
    if (amount > this.balance) {
      console.log("Insufficient funds");
      return false;
    }
    
    this.balance -= amount;
    console.log(`Withdrew ₦${amount}. New balance: ₦${this.balance}`);
    return true;
  }
  
  // Private helper method
  private verifyPin(inputPin: string): boolean {
    return this.pin === inputPin;
  }
  
  // Private method to calculate interest
  private calculateInterest(rate: number): number {
    return this.balance * rate;
  }
  
  public applyInterest(rate: number): void {
    const interest = this.calculateInterest(rate);
    this.balance += interest;
    console.log(`Applied ₦${interest} interest. New balance: ₦${this.balance}`);
  }
}

const account = new BankAccount("0123456789", 10000, "1234");

// ✅ Can access public property
console.log(account.accountNumber);  // "0123456789"

// ❌ Cannot access private properties directly
// console.log(account.balance);  // Error: Property 'balance' is private
// console.log(account.pin);      // Error: Property 'pin' is private

// ✅ Access private data through public methods
console.log(account.getBalance());  // 10000

account.deposit(5000);   // "Deposited ₦5000. New balance: ₦15000"
account.withdraw(3000, "1234");  // "Withdrew ₦3000. New balance: ₦12000"
account.withdraw(1000, "wrong");  // "Invalid PIN"

account.applyInterest(0.05);  // "Applied ₦600 interest. New balance: ₦12600"

// ❌ Cannot call private methods
// account.verifyPin("1234");  // Error: Method 'verifyPin' is private

Private Parameter Properties

private-parameter-properties.ts
class User {
  // Private parameter properties
  constructor(
    public username: string,
    private password: string,  // Private from the start
    private apiKey: string
  ) {}
  
  public authenticate(inputPassword: string): boolean {
    return this.password === inputPassword;
  }
  
  public changePassword(oldPassword: string, newPassword: string): boolean {
    if (this.password !== oldPassword) {
      return false;
    }
    
    if (newPassword.length < 8) {
      throw new Error("Password must be at least 8 characters");
    }
    
    this.password = newPassword;
    return true;
  }
  
  // API key is completely private - no public access
  private getApiKey(): string {
    return this.apiKey;
  }
}

const user = new User("yusuf", "secret123", "api-key-xyz");

console.log(user.username);  // ✅ "yusuf" (public)
// console.log(user.password);  // ❌ Error: private
// console.log(user.apiKey);    // ❌ Error: private

console.log(user.authenticate("secret123"));  // true
user.changePassword("secret123", "newpass456");

Protected Modifier

protected members are accessible within the class and its subclasses, but not from outside instances.

protected-modifier.ts
class Employee {
  public name: string;
  protected salary: number;  // Accessible in subclasses
  private ssn: string;       // Only in this class
  
  constructor(name: string, salary: number, ssn: string) {
    this.name = name;
    this.salary = salary;
    this.ssn = ssn;
  }
  
  public displayPublicInfo(): void {
    console.log(`Name: ${this.name}`);
  }
  
  protected displayProtectedInfo(): void {
    console.log(`Salary: ₦${this.salary}`);
  }
  
  private displayPrivateInfo(): void {
    console.log(`SSN: ${this.ssn}`);
  }
  
  public displayAllInfo(): void {
    this.displayPublicInfo();
    this.displayProtectedInfo();
    this.displayPrivateInfo();
  }
}

class Manager extends Employee {
  constructor(
    name: string,
    salary: number,
    ssn: string,
    public department: string
  ) {
    super(name, salary, ssn);
  }
  
  public giveBonusPercentage(percentage: number): void {
    // ✅ Can access protected salary in subclass
    const bonus = this.salary * (percentage / 100);
    this.salary += bonus;
    console.log(`Added ₦${bonus} bonus. New salary: ₦${this.salary}`);
  }
  
  public displayManagerInfo(): void {
    console.log(`Name: ${this.name}`);  // ✅ public
    
    // ✅ Can call protected method in subclass
    this.displayProtectedInfo();
    
    console.log(`Department: ${this.department}`);
    
    // ❌ Cannot access private members
    // console.log(`SSN: ${this.ssn}`);  // Error: private
    // this.displayPrivateInfo();          // Error: private method
  }
}

const employee = new Employee("Yusuf", 500000, "123-45-6789");
const manager = new Manager("Amina", 800000, "987-65-4321", "Engineering");

// ✅ Can access public members
console.log(employee.name);   // "Yusuf"
console.log(manager.name);    // "Amina"
console.log(manager.department);  // "Engineering"

// ❌ Cannot access protected members from outside
// console.log(employee.salary);  // Error: protected
// console.log(manager.salary);   // Error: protected

// ❌ Cannot access private members from outside
// console.log(employee.ssn);  // Error: private

// ❌ Cannot call protected methods from outside
// employee.displayProtectedInfo();  // Error: protected method

// ✅ Subclass can use protected members internally
manager.giveBonusPercentage(10);
// "Added ₦80000 bonus. New salary: ₦880000"

manager.displayManagerInfo();
// Name: Amina
// Salary: ₦880000
// Department: Engineering

Protected Constructors

protected-constructors.ts
// Protected constructor prevents direct instantiation
class Database {
  protected constructor(private connectionString: string) {
    console.log(`Connected to: ${connectionString}`);
  }
  
  protected query(sql: string): void {
    console.log(`Executing: ${sql}`);
  }
}

// ❌ Cannot instantiate directly
// const db = new Database("localhost");  // Error: constructor is protected

// ✅ Can extend and use in subclass
class MySQLDatabase extends Database {
  constructor(host: string, database: string) {
    super(`mysql://${host}/${database}`);
  }
  
  public executeQuery(sql: string): void {
    // Can call protected method
    this.query(sql);
  }
}

const mysql = new MySQLDatabase("localhost", "myapp");
mysql.executeQuery("SELECT * FROM users");

// Singleton pattern with protected constructor
class Logger {
  private static instance: Logger;
  
  protected constructor() {
    console.log("Logger initialized");
  }
  
  public static getInstance(): Logger {
    if (!Logger.instance) {
      Logger.instance = new Logger();
    }
    return Logger.instance;
  }
  
  public log(message: string): void {
    console.log(`[LOG] ${message}`);
  }
}

// ❌ Cannot create directly
// const logger1 = new Logger();  // Error: protected constructor

// ✅ Get singleton instance
const logger = Logger.getInstance();
logger.log("Application started");

const sameLogger = Logger.getInstance();
console.log(logger === sameLogger);  // true (same instance)

Readonly Modifier

readonly properties can only be assigned during initialization (declaration or constructor). They cannot be modified afterward.

readonly-modifier.ts
class User {
  readonly id: number;  // Can only set in constructor
  readonly createdAt: Date = new Date();  // Or at declaration
  public name: string;
  
  constructor(id: number, name: string) {
    this.id = id;
    this.name = name;
    
    // Can modify readonly in constructor
    this.createdAt = new Date();
  }
  
  public updateName(newName: string): void {
    this.name = newName;  // ✅ OK - not readonly
    
    // this.id = 999;  // ❌ Error: readonly
  }
}

const user = new User(1, "Yusuf");

console.log(user.id);         // 1
console.log(user.createdAt);  // Date

user.name = "Yusuf Ibrahim";  // ✅ OK
// user.id = 2;               // ❌ Error: readonly property
// user.createdAt = new Date();  // ❌ Error: readonly property

// Readonly with access modifiers
class Product {
  constructor(
    public readonly id: number,      // Public and readonly
    private readonly sku: string,    // Private and readonly
    public name: string
  ) {}
  
  public displayInfo(): void {
    console.log(`ID: ${this.id}`);
    console.log(`SKU: ${this.sku}`);  // Can read private readonly
    console.log(`Name: ${this.name}`);
  }
}

const product = new Product(101, "LAP-001", "Laptop");

console.log(product.id);    // ✅ 101 (public readonly)
// console.log(product.sku);  // ❌ Error: private
product.name = "Gaming Laptop";  // ✅ OK (not readonly)
// product.id = 102;          // ❌ Error: readonly

// Readonly arrays and objects
class Team {
  readonly members: readonly string[];  // Readonly array
  
  constructor(members: string[]) {
    this.members = members;
  }
}

const team = new Team(["Yusuf", "Amina", "Chidi"]);

console.log(team.members[0]);  // ✅ "Yusuf"
// team.members.push("David");  // ❌ Error: readonly array
// team.members[0] = "John";    // ❌ Error: readonly array

Readonly vs Const: Use readonly for class properties and const for variables. readonly can be set in the constructor, while const must be initialized at declaration.

Combining Access Modifiers

combining-modifiers.ts
class Order {
  // Different combinations
  public readonly orderId: number;           // Public, immutable
  private readonly createdAt: Date;          // Private, immutable
  protected readonly customerId: number;     // Protected, immutable
  private status: string;                    // Private, mutable
  public items: string[];                    // Public, mutable
  
  constructor(orderId: number, customerId: number) {
    this.orderId = orderId;
    this.customerId = customerId;
    this.createdAt = new Date();
    this.status = "pending";
    this.items = [];
  }
  
  // Public method accessing private data
  public getStatus(): string {
    return this.status;
  }
  
  public addItem(item: string): void {
    if (this.status === "shipped") {
      throw new Error("Cannot modify shipped order");
    }
    this.items.push(item);
  }
  
  // Protected method for subclasses
  protected updateStatus(newStatus: string): void {
    this.status = newStatus;
    console.log(`Order ${this.orderId} status: ${newStatus}`);
  }
  
  // Private helper method
  private validateStatusTransition(newStatus: string): boolean {
    const validTransitions: Record<string, string[]> = {
      pending: ["processing", "cancelled"],
      processing: ["shipped", "cancelled"],
      shipped: ["delivered"],
      delivered: [],
      cancelled: []
    };
    
    return validTransitions[this.status]?.includes(newStatus) ?? false;
  }
}

class AdminOrder extends Order {
  constructor(orderId: number, customerId: number) {
    super(orderId, customerId);
  }
  
  // Can access protected members
  public forceUpdateStatus(newStatus: string): void {
    console.log(`Admin override for customer ${this.customerId}`);
    this.updateStatus(newStatus);  // ✅ Protected method
  }
  
  // Cannot access private members
  public getCreationTime(): void {
    // console.log(this.createdAt);  // ❌ Error: private
    // console.log(this.status);     // ❌ Error: private
  }
}

const order = new Order(1001, 50);

// Public members
console.log(order.orderId);  // ✅ 1001
console.log(order.items);    // ✅ []
order.addItem("Laptop");     // ✅ Works

// Readonly public member
// order.orderId = 1002;  // ❌ Error: readonly

// Protected and private members
// console.log(order.customerId);  // ❌ Error: protected
// console.log(order.status);      // ❌ Error: private
// order.updateStatus("shipped");  // ❌ Error: protected method

// Access through public methods
console.log(order.getStatus());  // "pending"

const adminOrder = new AdminOrder(1002, 51);
adminOrder.forceUpdateStatus("delivered");  // Works in subclass

Practical Examples

Example 1: Secure Authentication System

auth-system.ts
class AuthenticationService {
  private readonly users: Map<string, string> = new Map();
  private readonly sessions: Map<string, { username: string; expiresAt: Date }> = new Map();
  private readonly sessionDuration: number = 3600000; // 1 hour
  
  public register(username: string, password: string): boolean {
    if (this.users.has(username)) {
      console.log("Username already exists");
      return false;
    }
    
    if (!this.validatePassword(password)) {
      console.log("Password does not meet requirements");
      return false;
    }
    
    const hashedPassword = this.hashPassword(password);
    this.users.set(username, hashedPassword);
    console.log(`User ${username} registered successfully`);
    return true;
  }
  
  public login(username: string, password: string): string | null {
    const storedHash = this.users.get(username);
    
    if (!storedHash) {
      console.log("User not found");
      return null;
    }
    
    if (this.hashPassword(password) !== storedHash) {
      console.log("Invalid password");
      return null;
    }
    
    const sessionId = this.generateSessionId();
    const expiresAt = new Date(Date.now() + this.sessionDuration);
    
    this.sessions.set(sessionId, { username, expiresAt });
    console.log(`User ${username} logged in`);
    return sessionId;
  }
  
  public logout(sessionId: string): boolean {
    const session = this.sessions.get(sessionId);
    
    if (!session) {
      return false;
    }
    
    this.sessions.delete(sessionId);
    console.log(`User ${session.username} logged out`);
    return true;
  }
  
  public validateSession(sessionId: string): boolean {
    const session = this.sessions.get(sessionId);
    
    if (!session) {
      return false;
    }
    
    if (new Date() > session.expiresAt) {
      this.sessions.delete(sessionId);
      return false;
    }
    
    return true;
  }
  
  // Private helper methods
  private validatePassword(password: string): boolean {
    return password.length >= 8 &&
           /[A-Z]/.test(password) &&
           /[a-z]/.test(password) &&
           /[0-9]/.test(password);
  }
  
  private hashPassword(password: string): string {
    // Simple hash for demo (use proper hashing in production)
    return `hashed_${password}`;
  }
  
  private generateSessionId(): string {
    return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

// Usage
const auth = new AuthenticationService();

// Register users
auth.register("yusuf", "SecurePass123");
auth.register("amina", "StrongPwd456");

// Login
const session1 = auth.login("yusuf", "SecurePass123");
if (session1) {
  console.log(`Session ID: ${session1}`);
  
  // Validate session
  console.log(auth.validateSession(session1));  // true
  
  // Logout
  auth.logout(session1);
  console.log(auth.validateSession(session1));  // false
}

// Cannot access private data
// console.log(auth.users);  // ❌ Error: private
// console.log(auth.hashPassword("test"));  // ❌ Error: private method

Example 2: Bank Account with Transaction History

bank-account-advanced.ts
type TransactionType = "deposit" | "withdrawal" | "transfer";

interface Transaction {
  readonly id: number;
  readonly type: TransactionType;
  readonly amount: number;
  readonly date: Date;
  readonly description: string;
}

class BankAccount {
  public readonly accountNumber: string;
  public readonly accountHolder: string;
  private balance: number;
  private readonly transactions: Transaction[] = [];
  private transactionIdCounter: number = 1;
  
  constructor(accountNumber: string, accountHolder: string, initialBalance: number = 0) {
    this.accountNumber = accountNumber;
    this.accountHolder = accountHolder;
    this.balance = initialBalance;
    
    if (initialBalance > 0) {
      this.recordTransaction("deposit", initialBalance, "Initial deposit");
    }
  }
  
  public deposit(amount: number): void {
    this.validateAmount(amount);
    
    this.balance += amount;
    this.recordTransaction("deposit", amount, "Deposit");
    console.log(`Deposited ₦${amount}. Balance: ₦${this.balance}`);
  }
  
  public withdraw(amount: number): boolean {
    this.validateAmount(amount);
    
    if (amount > this.balance) {
      console.log("Insufficient funds");
      return false;
    }
    
    this.balance -= amount;
    this.recordTransaction("withdrawal", amount, "Withdrawal");
    console.log(`Withdrew ₦${amount}. Balance: ₦${this.balance}`);
    return true;
  }
  
  public transfer(amount: number, recipient: BankAccount): boolean {
    if (!this.withdraw(amount)) {
      return false;
    }
    
    recipient.deposit(amount);
    this.updateLastTransaction(`Transfer to ${recipient.accountHolder}`);
    console.log(`Transferred ₦${amount} to ${recipient.accountHolder}`);
    return true;
  }
  
  public getBalance(): number {
    return this.balance;
  }
  
  public getTransactionHistory(): readonly Transaction[] {
    return [...this.transactions];  // Return copy
  }
  
  public getStatement(count: number = 10): void {
    console.log(`\n=== Statement for ${this.accountHolder} ===`);
    console.log(`Account: ${this.accountNumber}`);
    console.log(`Balance: ₦${this.balance}\n`);
    
    const recent = this.transactions.slice(-count).reverse();
    
    recent.forEach(transaction => {
      const sign = transaction.type === "deposit" ? "+" : "-";
      console.log(
        `[${transaction.date.toLocaleDateString()}] ` +
        `${transaction.description}: ${sign}₦${transaction.amount}`
      );
    });
    
    console.log("=".repeat(50) + "\n");
  }
  
  // Private helper methods
  private validateAmount(amount: number): void {
    if (amount <= 0) {
      throw new Error("Amount must be positive");
    }
  }
  
  private recordTransaction(type: TransactionType, amount: number, description: string): void {
    this.transactions.push({
      id: this.transactionIdCounter++,
      type,
      amount,
      date: new Date(),
      description
    });
  }
  
  private updateLastTransaction(description: string): void {
    const last = this.transactions[this.transactions.length - 1];
    if (last) {
      (last as any).description = description;  // Modify description
    }
  }
}

// Usage
const account1 = new BankAccount("0123456789", "Yusuf Ibrahim", 50000);
const account2 = new BankAccount("9876543210", "Amina Hassan", 30000);

account1.deposit(20000);
account1.withdraw(10000);
account1.transfer(5000, account2);

account1.getStatement();
// === Statement for Yusuf Ibrahim ===
// Account: 0123456789
// Balance: ₦55000
//
// [12/26/2025] Transfer to Amina Hassan: -₦5000
// [12/26/2025] Withdrawal: -₦10000
// [12/26/2025] Deposit: +₦20000
// [12/26/2025] Initial deposit: +₦50000

console.log(account2.getBalance());  // 35000

// Cannot access private data
// console.log(account1.balance);  // ❌ Error: private
// console.log(account1.transactions);  // ❌ Error: private

// Readonly property
console.log(account1.accountNumber);  // ✅ "0123456789"
// account1.accountNumber = "111";    // ❌ Error: readonly

Test Your Knowledge

Type Safety Check

Which access modifier usage is correct?

class BankAccount {
  // Option A
  public accountNumber: string;
  private balance: number;
  protected customerName: string;

  constructor(accountNumber: string, customerName: string) {
    this.accountNumber = accountNumber;
    this.balance = 0;
    this.customerName = customerName;
  }

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

  public getBalance(): number {
    return this.balance;
  }
}

const account = new BankAccount("0123456789", "Yusuf");

// Which is valid?
// Option B
console.log(account.accountNumber);  // Access public property

// Option C
console.log(account.balance);  // Access private property

// Option D
console.log(account.customerName);  // Access protected property

Common TypeScript Error

Code with Error
class User {
  private password: string;
  protected email: string;
  
  constructor(email: string, password: string) {
    this.email = email;
    this.password = password;
  }
}

const user = new User("yusuf@example.com", "secret123");

// Trying to access private property
console.log(user.password);  // ❌ Error!

// Trying to access protected property
console.log(user.email);  // ❌ Error!

// Trying to modify private property
user.password = "newpassword";  // ❌ Error!

❌ Property 'password' is private and only accessible within class 'User'. Property 'email' is protected and only accessible within class 'User' and its subclasses.

What's Wrong?

Private properties can only be accessed inside the class. Protected properties can be accessed in the class and subclasses, but not from outside instances. This is encapsulation in action!

Corrected Code
class User {
  private password: string;
  protected email: string;
  
  constructor(email: string, password: string) {
    this.email = email;
    this.password = password;
  }
  
  // Public method to verify password
  public verifyPassword(inputPassword: string): boolean {
    return this.password === inputPassword;
  }
  
  // Public method to update password
  public updatePassword(oldPassword: string, newPassword: string): boolean {
    if (this.password !== oldPassword) {
      return false;
    }
    
    if (newPassword.length < 8) {
      throw new Error("Password must be at least 8 characters");
    }
    
    this.password = newPassword;
    return true;
  }
  
  // Public method to get email
  public getEmail(): string {
    return this.email;
  }
}

const user = new User("yusuf@example.com", "secret123");

// ✅ Access through public methods
console.log(user.verifyPassword("secret123"));  // true
console.log(user.verifyPassword("wrong"));      // false

user.updatePassword("secret123", "newsecret456");
console.log(user.verifyPassword("newsecret456"));  // true

console.log(user.getEmail());  // "yusuf@example.com"

Solution: Provide public methods to access or modify private/protected data

Best Practices

  1. Default to private - only make public what needs to be
  2. Use protected for inheritance - share with subclasses only
  3. Make IDs and timestamps readonly - prevent accidental changes
  4. Provide public methods for private data - controlled access
  5. Validate in public methods - protect internal state
  6. Use private for helper methods - hide implementation details
  7. Be explicit with modifiers - improve readability
  8. Combine readonly with private/protected - maximum protection
best-practices-example.ts
// ✅ Good: Proper encapsulation
class Product {
  public readonly id: number;        // Public, immutable
  private price: number;             // Private, controlled access
  protected stock: number;           // For subclasses
  
  constructor(id: number, price: number, stock: number) {
    this.id = id;
    this.price = this.validatePrice(price);
    this.stock = stock;
  }
  
  // Public getter with validation
  public getPrice(): number {
    return this.price;
  }
  
  // Public setter with validation
  public updatePrice(newPrice: number): void {
    this.price = this.validatePrice(newPrice);
  }
  
  // Private validation
  private validatePrice(price: number): number {
    if (price < 0) {
      throw new Error("Price cannot be negative");
    }
    return price;
  }
}

// ❌ Avoid: Everything public
class BadProduct {
  public id: number;      // Should be readonly
  public price: number;   // Should be private
  public stock: number;   // Should be protected/private
  
  constructor(id: number, price: number, stock: number) {
    this.id = id;
    this.price = price;
    this.stock = stock;
    // No validation!
  }
}

// ❌ Avoid: Everything private
class TooPrivate {
  private name: string;
  private email: string;
  
  constructor(name: string, email: string) {
    this.name = name;
    this.email = email;
  }
  
  // No way to access data!
}

Key Takeaways

  • public members are accessible everywhere (default)
  • private members are only accessible within the class
  • protected members are accessible in class and subclasses
  • readonly properties can only be set during initialization
  • Access modifiers enable encapsulation and data hiding
  • Use private for internal implementation details
  • Use protected to share with subclasses
  • Combine modifiers for fine-grained control
  • Provide public methods to access private/protected data
  • Default to private, make public only when necessary

What's Next?

Excellent work! You've mastered access modifiers and understand how to properly encapsulate your classes. Next, we'll explore Inheritance and Polymorphism—how to extend classes, reuse code, and implement polymorphic behavior using the extends keyword and method overriding. These are fundamental OOP concepts!

Get ready to master inheritance and build powerful class hierarchies!

Mastering TypeScript access modifiers! Check this out!

Previous
Classes and Constructors
Next
Inheritance and Polymorphism

Never Miss a New TypeScript Tutorial

Join 1,000+ developers learning TypeScript step-by-step. Get new tutorials, type safety tips, and exclusive resources delivered to your inbox - completely FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

TypeScript Tutorials

0 of 30 completed

Your Progress0%

Topics

Getting Started

  • What is TypeScript?
  • TypeScript vs JavaScript
  • Setting Up TypeScript
  • Your First TypeScript Program

Basic Types

  • Primitive Types
  • Arrays and Tuples
  • Objects and Type Aliases
  • Enums
  • Any, Unknown, and Never

Functions & Interfaces

  • Function Types and Signatures
  • Interfaces
  • Optional and Default Parameters
  • Function Overloading

Advanced Types

  • Union and Intersection Types
  • Type Guards and Narrowing
  • Literal Types
  • Type Assertions and Casting

Generics

  • Introduction to Generics
  • Generic Constraints
  • Utility Types

Classes & OOP

  • Classes and Constructors
  • Access Modifiers
  • Inheritance and Polymorphism
  • Abstract Classes and Interfaces

Practical TypeScript

  • Working with Modules
  • TypeScript with React
  • Async/Await and Promises
  • Working with APIs and JSON
  • Type Declarations and DefinitelyTyped
  • TypeScript Best Practices
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo