LearnFrenzy
Sign in Take a free test

Classes and Modules · Lesson 13 of 17

TypeScript Classes and Access Modifiers

4 min read Updated Nov 8, 2026 Saurabh Samir

JavaScript has had classes since ES2015. TypeScript adds types to their fields and methods, plus access modifiers, readonly, abstract classes and implements. Angular is built around classes (components, services, guards), NestJS on the Node side too – so this is everyday TypeScript for many teams, even if React code leans more on functions.

In this lesson

  • Typed fields, constructors and methods
  • public, private, protected and JavaScript's #private
  • readonly, parameter properties and getters
  • Abstract classes and implements

A typed class

class BankAccount {
  private balance = 0;                       // type inferred as number
  readonly accountNo: string;
  static readonly minDeposit = 100;          // belongs to the class, not each object

  constructor(accountNo: string, public owner: string) {   // 'public owner' declares and assigns a field
    this.accountNo = accountNo;
  }

  deposit(amount: number): this {            // returning this allows chaining
    if (amount < BankAccount.minDeposit) throw new Error(`minimum deposit is ${BankAccount.minDeposit}`);
    this.balance += amount;
    return this;
  }

  get currentBalance(): number {             // read like a property, no setter = read-only
    return this.balance;
  }
}

const acc = new BankAccount("SB-1001", "Asha").deposit(500).deposit(250);
console.log(acc.owner, acc.accountNo, acc.currentBalance);
try {
  acc.deposit(50);
} catch (e) {
  console.log((e as Error).message);
}

Output

Asha SB-1001 750
minimum deposit is 100

What the modifiers prevent

class BankAccount {
  private balance = 0;
  readonly accountNo = "SB-1001";
  get currentBalance(): number { return this.balance; }
}
const acc = new BankAccount();
acc.balance = 1_000_000;
acc.accountNo = "SB-9999";
acc.currentBalance = 5;

Type errors

main.ts(7,5): error TS2341: Property 'balance' is private and only accessible within class 'BankAccount'.
main.ts(8,5): error TS2540: Cannot assign to 'accountNo' because it is a read-only property.
main.ts(9,5): error TS2540: Cannot assign to 'currentBalance' because it is a read-only property.
ModifierAccessible fromEnforced
public (default)anywhere–
protectedthe class and its subclassescompile time only
privatethe class onlycompile time only
#fieldthe class onlyat run time too (JavaScript feature)

private is a compile-time promise

TypeScript's private disappears in the emitted JavaScript, so (acc as any).balance still works at run time. If you need real privacy (a library handling secrets, for example), use JavaScript's #balance syntax, which the engine enforces.

Inheritance and protected

class Employee {
  constructor(public readonly name: string, protected baseSalary: number) {}

  monthlyPay(): number {
    return this.baseSalary / 12;
  }
  describe(): string {
    return `${this.name}: ${this.monthlyPay().toFixed(0)} per month`;
  }
}

class Manager extends Employee {
  constructor(name: string, baseSalary: number, private teamSize: number) {
    super(name, baseSalary);                 // must call super before using this
  }

  override monthlyPay(): number {            // 'override' catches typos in the method name
    return super.monthlyPay() + this.teamSize * 2000;   // protected baseSalary used via super
  }
}

const staff: Employee[] = [new Employee("Ravi", 600000), new Manager("Meera", 1200000, 5)];
for (const e of staff) console.log(e.describe());

Output

Ravi: 50000 per month
Meera: 110000 per month

Abstract classes and implements

interface Notifier {
  send(to: string, message: string): string;
}

abstract class BaseNotifier implements Notifier {
  private sent = 0;
  protected abstract channel: string;                 // subclasses must provide these
  protected abstract format(message: string): string;

  send(to: string, message: string): string {         // shared logic lives in the base class
    this.sent++;
    return `[${this.channel} #${this.sent}] to ${to}: ${this.format(message)}`;
  }
}

class EmailNotifier extends BaseNotifier {
  protected channel = "email";
  protected format(message: string): string {
    return `Hello, ${message}`;
  }
}

class SmsNotifier extends BaseNotifier {
  protected channel = "sms";
  protected format(message: string): string {
    return message.slice(0, 20);
  }
}

const notifiers: Notifier[] = [new EmailNotifier(), new SmsNotifier()];
for (const n of notifiers) console.log(n.send("asha", "Your order #4521 has been shipped"));
// new BaseNotifier();   // error: cannot create an instance of an abstract class

Output

[email #1] to asha: Hello, Your order #4521 has been shipped
[sms #1] to asha: Your order #4521 has

The notifier hierarchy

  • Notifier (interface: the contract)
    • BaseNotifier (abstract: shared send logic)
      • EmailNotifier
      • SmsNotifier

Interface or abstract class?

An interface is only a contract and leaves nothing in the JavaScript. An abstract class can hold shared code and state. Code against the interface (Notifier[]), and use an abstract class when several implementations share real logic.

Practice

Try it yourself

  1. Write a Counter class with a truly private #count, increment() returning this, and a value getter.

    Show solution
    class Counter {
      #count = 0;
      increment(): this {
        this.#count++;
        return this;
      }
      get value(): number {
        return this.#count;
      }
    }
    const c = new Counter().increment().increment().increment();
    console.log(c.value, Object.keys(c));

    Output

    3 []

Quick check

What does constructor(private name: string) do?

It declares a private field name and assigns the argument to it – a parameter property.

Is TypeScript's private enforced at run time?

No, only at compile time. #field is enforced at run time.

Can you write new on an abstract class?

No – only on concrete subclasses that implement every abstract member.

Summary

  • Classes get typed fields, readonly, static members, getters and parameter properties.
  • public/protected/private are compile-time checks; #private is enforced by JavaScript.
  • override marks overridden methods; abstract classes share code; implements checks a class against an interface.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links