Classes, Access Modifiers & Abstract Classes

0/5 in this phase0/21 across the roadmap

📖 Concept

TypeScript enhances JavaScript classes with access modifiers, abstract classes, parameter properties, and interface implementation.

Access modifiers:

  • public — Accessible anywhere (default)
  • private — Only within the class itself (TS-only enforcement)
  • protected — Within the class and its subclasses
  • #field — True JavaScript private (runtime enforcement, ES2022)

Parameter properties are a shorthand: constructor(public name: string) automatically creates and assigns this.name.

Abstract classes define blueprints that can't be instantiated directly — subclasses MUST implement abstract methods.

Interface implementation (class Foo implements Bar) ensures a class satisfies a contract. Unlike structural typing, implements provides an explicit check.

override keyword (TS 4.3) explicitly marks methods overriding parent methods — catches typos in method names.

🏠 Real-world analogy: Access modifiers are like building security levels. public is the lobby — anyone can enter. protected is the office floor — only employees and their teams. private is the server room — only specific personnel. Abstract classes are like blueprints — you can't live in a blueprint, but you build real houses from them.

💻 Code Example

codeTap to expand ⛶
1// Access modifiers + parameter properties
2class User {
3 // Parameter properties — shorthand for declare + assign
4 constructor(
5 public readonly id: number,
6 public name: string,
7 private email: string,
8 protected role: string = "user"
9 ) {}
10
11 getEmail(): string {
12 return this.email; // ✅ private accessible inside class
13 }
14}
15
16const user = new User(1, "Alice", "alice@test.com");
17user.name; // ✅ public
18// user.email; // ❌ Error: 'email' is private
19// user.role; // ❌ Error: 'role' is protected
20// user.id = 2; // ❌ Error: readonly
21
22// Protected access in subclass
23class Admin extends User {
24 constructor(id: number, name: string, email: string) {
25 super(id, name, email, "admin");
26 }
27
28 getRole(): string {
29 return this.role; // ✅ protected accessible in subclass
30 }
31}
32
33// Abstract class — can't instantiate directly
34abstract class Shape {
35 abstract area(): number; // Must be implemented
36 abstract perimeter(): number; // Must be implemented
37
38 // Concrete method — shared by all subclasses
39 describe(): string {
40 return `Shape with area ${this.area().toFixed(2)}`;
41 }
42}
43
44// const s = new Shape(); // ❌ Error: Cannot create instance of abstract class
45
46class Circle extends Shape {
47 constructor(public radius: number) {
48 super();
49 }
50
51 area(): number {
52 return Math.PI * this.radius ** 2;
53 }
54
55 perimeter(): number {
56 return 2 * Math.PI * this.radius;
57 }
58}
59
60// Implements — class satisfies an interface
61interface Serializable {
62 serialize(): string;
63 deserialize(data: string): void;
64}
65
66interface Loggable {
67 log(): void;
68}
69
70class Config implements Serializable, Loggable {
71 constructor(private data: Record<string, unknown> = {}) {}
72
73 serialize(): string {
74 return JSON.stringify(this.data);
75 }
76
77 deserialize(data: string): void {
78 this.data = JSON.parse(data);
79 }
80
81 log(): void {
82 console.log(this.data);
83 }
84}
85
86// Override keyword (TS 4.3+, enable noImplicitOverride)
87class Animal {
88 speak(): string {
89 return "...";
90 }
91}
92
93class Dog extends Animal {
94 override speak(): string { // Explicit override
95 return "Woof!";
96 }
97
98 // override spek(): string { // ❌ Error: no method named 'spek' to override
99 // return "typo caught!";
100 // }
101}
102
103// Generic class
104class Repository<T extends { id: number }> {
105 private items = new Map<number, T>();
106
107 add(item: T): void {
108 this.items.set(item.id, item);
109 }
110
111 findById(id: number): T | undefined {
112 return this.items.get(id);
113 }
114
115 getAll(): T[] {
116 return Array.from(this.items.values());
117 }
118}
119const userRepo = new Repository<User>();

🏋️ Practice Exercise

Mini Exercise:

  1. Create a class with public, private, protected, and readonly properties — test each from outside
  2. Use parameter properties to simplify a constructor
  3. Create an abstract Vehicle class with concrete and abstract methods, then implement Car and Truck
  4. Implement multiple interfaces on a single class
  5. Use the override keyword and see what happens with a typo in the method name

⚠️ Common Mistakes

  • Using TS private for security — it's compile-time only; at runtime, the property is still accessible. Use #field for true runtime privacy

  • Forgetting to call super() in the subclass constructor before using this — TypeScript enforces this and throws a compile error

  • Not knowing that implements doesn't add anything at runtime — it's a compile-time check only. The class must still provide all properties

  • Over-using abstract classes when an interface would suffice — abstract classes add a runtime prototype chain; interfaces are zero-cost

  • Forgetting that parameter properties only work in constructors — public on a regular method parameter doesn't create a property

💼 Interview Questions

🎤 Mock Interview

Practice a live interview for Classes, Access Modifiers & Abstract Classes