Topics
v5.0.0
On this page · sections

Classes, this & Advanced OOP Types

TypeScript classes add access modifiers, abstract contracts, and this-aware typing on top of JavaScript’s prototype model. Senior frontend interviews test whether you understand when classes beat plain objects — and how this types prevent fluent API bugs.

ConceptPurposeJS equivalent
public / private / protectedEncapsulation#private fields (runtime)
abstractUninstantiable base + required methodsNo direct equivalent
implementsStructural contract checkDuck typing at compile time
this return typeFluent chaining preserves subclassManual casting without types
readonlyImmutable properties after initObject.freeze (shallow)

Access modifiers

Definition

class BankAccount { public readonly id: string; private balance: number; protected owner: string; constructor(id: string, owner: string, initial: number) { this.id = id; this.owner = owner; this.balance = initial; } public deposit(amount: number): void { this.balance += amount; } public getBalance(): number { return this.balance; } } const acct = new BankAccount("1", "Ada", 100); acct.deposit(50); // acct.balance; // ❌ Property 'balance' is private
  • public — accessible everywhere (default).
  • private — only inside the declaring class (TS compile-time; use #field for runtime privacy).
  • protected — class + subclasses.

Interview: private in TypeScript is erased at compile time — it’s not true runtime privacy. Use #privateField when you need runtime enforcement.

readonly and parameter properties

readonly fields cannot be reassigned after initialization. With strictPropertyInitialization, fields must be set in the constructor or at declaration.

class Point { constructor( public readonly x: number, public readonly y: number, ) {} }

Abstract classes

Definition

Abstract classes cannot be instantiated directly — they define a partial implementation and force subclasses to fill in abstract members.

abstract class DataSource { abstract fetch(): Promise<unknown[]>; abstract getName(): string; async loadAll(): Promise<unknown[]> { const data = await this.fetch(); console.log(`Loaded ${data.length} from ${this.getName()}`); return data; } } class RestDataSource extends DataSource { constructor(private baseUrl: string) { super(); } getName() { return this.baseUrl; } async fetch() { const res = await fetch(this.baseUrl); return res.json(); } } // const ds = new DataSource(); // ❌ Cannot create an instance of an abstract class const users = new RestDataSource("/api/users");

Interview: Use abstract class when you share concrete methods + state. Use interface when you only need a shape contract with no shared implementation.

implements and structural typing

Definition

Classes implement interfaces — TypeScript checks structure, not inheritance:

interface Serializable { toJSON(): string; } interface Timestamped { createdAt: Date; } class EventLog implements Serializable, Timestamped { constructor( public message: string, public createdAt: Date = new Date(), ) {} toJSON() { return JSON.stringify({ message: this.message, createdAt: this.createdAt }); } }

Missing a member is a compile error. Extra members are allowed.

InstanceType<typeof C> extracts the instance type from a class constructor — useful in factory patterns.

Polymorphism and method overrides

Definition

Subclasses can override methods with compatible signatures:

class Shape { area(): number { return 0; } } class Circle extends Shape { constructor(private radius: number) { super(); } override area(): number { return Math.PI * this.radius ** 2; } } function printArea(shape: Shape) { console.log(shape.area()); } printArea(new Circle(5)); // polymorphic call resolves to Circle.area

Use the override keyword (TS 4.3+) to catch typos when the base class has no matching method.

this types

Definition

Annotating a method’s return as this preserves the actual subclass in fluent APIs:

class QueryBuilder { private filters: string[] = []; where(condition: string): this { this.filters.push(condition); return this; } orderBy(field: string): this { this.filters.push(`ORDER BY ${field}`); return this; } build(): string { return this.filters.join(" AND "); } } class ExtendedQueryBuilder extends QueryBuilder { limit(n: number): this { this.filters.push(`LIMIT ${n}`); return this; } } const q = new ExtendedQueryBuilder() .where("status = 'active'") .orderBy("created_at") .limit(10) // ✅ ExtendedQueryBuilder methods preserved .build();

Without this, returning QueryBuilder would lose subclass methods after the first chained call.

this parameters

class Counter { count = 0; increment(this: Counter) { this.count++; } } const counter = new Counter(); counter.increment(); const fn = counter.increment; // fn(); // ❌ The 'this' context is not assignable

Interview: this return types are essential for builder patterns, ORM query APIs, and any method that returns this for chaining.

Classes vs functions in frontend code

Classes fit stateful services (WebSocket managers, SDK clients). React UI and pure transforms prefer functions + typed objects:

type UserService = { getUser: (id: string) => Promise<User>; updateUser: (id: string, data: Partial<User>) => Promise<User>; }; function createUserService(client: ApiClient): UserService { return { getUser: (id) => client.get(`/users/${id}`), updateUser: (id, data) => client.patch(`/users/${id}`, data), }; }

Senior Interview Summary

What to say

TypeScript classes add compile-time access control, abstract contracts, and this-aware return types for fluent APIs. In modern React codebases, classes appear in services and SDKs; UI is usually functions. Know implements for structural contracts and InstanceType for factory typing.

Checklist

private vs #field → compile-time vs runtime privacy abstract class → shared impl + forced overrides override keyword → catches base-class typos return this → preserves subclass in chains strictPropertyInitialization → fields must be assigned

Anti-patterns

Class for every React component → hooks + functions scale better public everything → defeats encapsulation purpose Returning base type from chain → breaks subclass methods Deep inheritance trees → prefer composition + interfaces

Practical rule

UI layer → function components + typed props Services / SDKs → classes or factory objects with interfaces Fluent builders → this return type on every chain method Shared contracts → interface + implements, not deep extends

Last updated on