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.
| Concept | Purpose | JS equivalent |
|---|---|---|
public / private / protected | Encapsulation | #private fields (runtime) |
abstract | Uninstantiable base + required methods | No direct equivalent |
implements | Structural contract check | Duck typing at compile time |
this return type | Fluent chaining preserves subclass | Manual casting without types |
readonly | Immutable properties after init | Object.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 privatepublic— accessible everywhere (default).private— only inside the declaring class (TS compile-time; use#fieldfor runtime privacy).protected— class + subclasses.
Interview:
privatein TypeScript is erased at compile time — it’s not true runtime privacy. Use#privateFieldwhen 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 classwhen you share concrete methods + state. Useinterfacewhen 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.areaUse 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 assignableInterview:
thisreturn types are essential for builder patterns, ORM query APIs, and any method that returnsthisfor 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. Knowimplementsfor structural contracts andInstanceTypefor 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 assignedAnti-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 + interfacesPractical 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