Chapter 4.3☕ 18 min read

Classes & Access Modifiers

Data, behavior, and who sees what

01The Golconda Fort Zones

Interfaces define the SHAPE of data — what properties an object has. But what about BEHAVIOR? How do you bundle data and the functions that operate on that data together? That's where classes come in. Classes bring both data and behavior together — they are blueprints for creating objects with properties AND methods.

And with access modifiers — public, private, and protected — you control WHO can access what inside your class. This is crucial for building robust, maintainable code where internal details stay internal.

Think of the Nizam's Golconda Fort. The fort has different zones with different security levels. The main courtyard where everyone can walk freely — merchants, soldiers, visitors — that's public. Anyone can access it from anywhere. The inner chambers where only the Nizam's family can go — that's protected. Only the family (subclasses) gets access. And the Nizam's personal treasury vault — that's private. ONLY the Nizam himself (the class) can access it. Not the family, not the guards, not the public — nobody else.

Access modifiers are the fort's security system — they enforce boundaries at compile time. TypeScript checks that nobody reaches where they shouldn't. Seedha samjho: class = data + behavior, modifiers = who sees what! If you try to access a private property from outside, TypeScript will stop you right at the gate. This keeps your code safe and predictable, just like the fort's zones keep everyone in their proper place.

02Class Basics

Classes create objects with both state (properties) and behavior (methods). Here's a basic TypeScript class:

class Biryani {
  type: string;
  price: number;
  constructor(
    type: string,
    price: number
  ) {
    this.type = type;
    this.price = price;
  }
  serve(): string {
    return `Serving ${this.type}`;
  }
}

Creating instances works exactly like JavaScript:

const chicken = new Biryani(
  "chicken", 350
);
chicken.serve(); // "Serving chicken"

TypeScript has a powerful shorthand — parameter properties. Put a modifier like public in the constructor parameter, and TypeScript AUTOMATICALLY declares AND assigns the property:

class Biryani {
  constructor(
    public type: string,
    public price: number
  ) {}
}

// Same as the long version above!
const b = new Biryani("mutton", 400);
b.type;  // "mutton"
b.price; // 400

This is a TypeScript-only feature — it does not exist in plain JavaScript. It saves you from writing the same property name three times (declaration, parameter, assignment).

You can also use readonly with classes for properties that should never change after creation:

class User {
  constructor(
    public name: string,
    readonly id: string
  ) {}
}

const u = new User("Imran", "1");
u.name = "Ahmed"; // OK
u.id = "2";       // ERROR! readonly

And here's something important: classes ARE types! The class name works as a type annotation:

const u: User = new User("Imran", "1");
// User is both a value AND a type
03Access Modifiers

Three access modifiers control visibility in TypeScript classes. Let's explore each one in depth.

public (the default) — accessible everywhere: from the class itself, from subclasses, and from outside code. You rarely need to write it explicitly since it's the default, but being explicit can improve readability.

private — accessible ONLY inside the class that defines it. Not from subclasses, not from outside. Period.

protected — accessible from the class AND its subclasses, but NOT from outside code.

Here's the Golconda Fort example putting all three together:

class Vault {
  private secretKey = "gold123";
  protected familyCode = "nizam007";
  public fortName = "Golconda";

  getSecret() {
    return this.secretKey;
  }
}

class ExtendedVault extends Vault {
  getFamilyCode() {
    return this.familyCode; // OK
  }
  // return this.secretKey;
  // ERROR - private!
}

const v = new Vault();
v.fortName;   // OK (public)
v.familyCode; // ERROR (protected)
v.secretKey;  // ERROR (private)
v.getSecret(); // OK (public method)

Notice the pattern: a public method (getSecret) can internally access a private property and return it. This is a common pattern — you control access through methods, not direct property access.

Critical: TypeScript's private is compile-time only! At runtime in JavaScript, all properties are still accessible. TypeScript erases the private keyword during compilation. For true runtime privacy, use JavaScript's #private syntax:

class Vault {
  #realSecret = "gold";
  get hint() {
    return this.#realSecret;
  }
}

const v = new Vault();
v.#realSecret; // SyntaxError at runtime!
v.hint;        // "gold" — controlled access

The # syntax is a real ECMAScript feature that enforces privacy at runtime. TypeScript's private is a type-system feature only. Use # when you need genuine privacy enforcement beyond the type checker.

04Class Traps

Classes in TypeScript come with several common traps that catch developers off guard, especially those coming from JavaScript. Let's walk through each one.

Trap 1: Forgetting to declare properties. In JavaScript, you can just write this.name = name in the constructor without any declaration. In TypeScript, you MUST declare the property first, or use parameter properties:

// ERROR - property not declared
class X {
  constructor(name: string) {
    this.name = name; // ERROR!
  }
}

// FIX 1: Declare explicitly
class X1 {
  name: string;
  constructor(name: string) {
    this.name = name; // OK
  }
}

// FIX 2: Parameter property
class X2 {
  constructor(public name: string) {}
}

Trap 2: Accessing private from outside. TypeScript blocks this at compile time, but at runtime the property is still there:

class A {
  private x = 1;
}
const a = new A();
// a.x; // TS ERROR at compile time
// But at runtime: a.x === 1 !

Don't rely on TypeScript's private for actual security — it's a type-level constraint only!

Trap 3: Private vs protected confusion. Private means "only THIS class." Protected means "this class AND subclasses." If you need subclasses to access something, it must be protected, not private:

class Parent {
  private secret = "mine";
  protected shared = "ours";
}
class Child extends Parent {
  read() {
    // this.secret; // ERROR
    this.shared;    // OK
  }
}

Trap 4: Parameter property without modifier. Writing constructor(name: string) does NOT create a property. You need the modifier keyword: constructor(public name: string). Without public, private, or protected, it's just a normal parameter that vanishes after the constructor runs.

Trap 5: Readonly vs private. These serve completely different purposes! readonly means "can't be modified but CAN be read from outside." private means "can't be accessed from outside at all." Use readonly for immutable data you want to expose. Use private for internal details you want to hide.

05Classes Cheatsheet

Let's recap everything with a clean cheatsheet for classes and access modifiers.

Class Syntax:

class Name {
  // Property declarations
  prop: type;
  // Constructor
  constructor(param: type) {
    this.prop = param;
  }
  // Methods
  method(): ReturnType { }
}

Parameter Properties (shorthand):

class Name {
  constructor(
    public x: string,
    private y: number,
    protected z: boolean,
    readonly id: string
  ) {}
}

Access Modifiers Cheatsheet:

// PUBLIC - default, everywhere
public name: string;

// PRIVATE - class only
private secret: string;

// PROTECTED - class + subclasses
protected familyCode: string;

Privacy Comparison:

// TS private (compile-time only)
private x = 1;
// Erased at runtime, still accessible

// JS #private (runtime privacy)
#x = 1;
// SyntaxError if accessed outside

Key Rules to Remember:

  • Always declare properties or use parameter properties with modifiers.
  • public is the default — you don't need to write it explicitly, but being explicit can aid readability.
  • Private = Nizam's vault (class only), Protected = family chambers (class + subclasses), Public = fort courtyard (everyone).
  • TypeScript's private is compile-time only — it's erased at runtime.
  • Use JavaScript's # syntax for true runtime privacy enforcement.
  • readonly prevents modification but allows external reads — different from private.

The golden rule: "Classes are your Golconda Fort — structure with security. Public for the courtyard, protected for the family, private for the vault. Set the boundaries right, bhai!"

Key Takeaways

  • Classes bundle data (properties) and behavior (methods) together
  • Parameter properties (`constructor(public x: string)`) declare and assign in one line
  • `public` = everywhere, `private` = class only, `protected` = class + subclasses
  • TypeScript `private` is compile-time only — use `#` for runtime privacy
  • `readonly` prevents modification but allows reads; `private` prevents external access entirely
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase