1. What is an Abstract Class?#
You declare an abstract class by prefixing the class keyword with abstract.
Abstract classes cannot be directly instantiated using new. Their sole purpose is to be extended by concrete subclasses via extends.
abstract class BaseHttpClient {
constructor(public readonly baseUrl: string) {}
// Concrete method shared by all subclasses
public buildFullUrl(endpoint: string): string {
return `${this.baseUrl.replace(/\/$/, "")}/${endpoint.replace(/^\//, "")}`;
}
}
// ❌ Compiler Error: Cannot create an instance of an abstract class.
// const client = new BaseHttpClient("https://api.acme.com");
// 🟢 WORKED: Derived concrete subclass can be instantiated!
class UserApiClient extends BaseHttpClient {
public async fetchUser(id: string) {
const url = this.buildFullUrl(`users/${id}`);
console.log(`Fetching from: ${url}`);
}
}
const api = new UserApiClient("https://api.acme.com");
api.fetchUser("usr_99"); // "Fetching from: https://api.acme.com/users/usr_99"
2. Abstract Methods and Properties#
An abstract class can contain Abstract Members (marked with the abstract keyword).
Abstract methods and properties do not have an implementation body in the base class. They act as mandatory contracts that every derived subclass MUST implement.
abstract class DatabaseAdapter {
// Abstract Property Contract
public abstract readonly engineName: string;
// Abstract Method Contract (No function body!)
public abstract connect(): Promise<void>;
public abstract executeQuery(query: string): Promise<unknown[]>;
}
// Subclass fulfilling the abstract contracts
class PostgresAdapter extends DatabaseAdapter {
// Implementing abstract property
public override readonly engineName = "PostgreSQL";
// Implementing abstract methods
public override async connect(): Promise<void> {
console.log(`Connecting to ${this.engineName} engine...`);
}
public override async executeQuery(query: string): Promise<unknown[]> {
console.log(`Executing SQL on ${this.engineName}: ${query}`);
return [{ id: 101, result: "OK" }];
}
}If a subclass forgets to implement an abstract method or property, TypeScript throws a compile-time error:
// ❌ Compiler Error: Non-abstract class 'BadAdapter' does not implement
// inherited abstract member 'executeQuery' from class 'DatabaseAdapter'.
class BadAdapter extends DatabaseAdapter {
public override readonly engineName = "BadEngine";
public override async connect() {}
// Missing executeQuery()!
}3. The Template Method Design Pattern#
Abstract classes are the natural habitat for the classic Template Method Design Pattern.
In this pattern, the abstract base class defines the skeleton of an algorithm in a high-level method (the Template Method), while delegating specific sub-steps to abstract methods that subclasses must override.
abstract class DataPipeline {
// The Template Method (Defines algorithm flow)
public async runPipeline(): Promise<void> {
console.log("[Pipeline]: Step 1 - Extracting raw data...");
const rawData = await this.extractData();
console.log("[Pipeline]: Step 2 - Transforming data payload...");
const transformed = this.transformData(rawData);
console.log("[Pipeline]: Step 3 - Loading into destination...");
await this.loadData(transformed);
console.log("[Pipeline]: Complete!");
}
// Abstract Sub-Steps (Subclasses MUST implement these specific details)
protected abstract extractData(): Promise<string>;
protected abstract transformData(raw: string): string[];
protected abstract loadData(items: string[]): Promise<void>;
}
// Concrete CSV Pipeline implementation
class CsvDataPipeline extends DataPipeline {
protected override async extractData(): Promise<string> {
return "id,name\n1,Alice\n2,Bob";
}
protected override transformData(raw: string): string[] {
return raw.split("\n").slice(1); // Exclude header line
}
protected override async loadData(items: string[]): Promise<void> {
console.log(`Loaded ${items.length} records into Database.`);
}
}
const pipeline = new CsvDataPipeline();
pipeline.runPipeline();4. Abstract Classes vs Interfaces#
| Feature | interface | abstract class |
|---|---|---|
| JS Compiled Footprint | Erased completely (0 bytes) | Emits JS Class constructor function |
| Concrete Code Reuse | No (Cannot contain code implementation) | Yes (Can contain concrete shared methods/fields) |
| Instantiability | Cannot be instantiated | Cannot be instantiated |
| Class Relationship | Implemented via implements (Multiple) | Extended via extends (Single parent only) |
| Access Modifiers | Only public declarations | public, protected, private, abstract |
Summary & Next Steps#
In this episode:
- We defined non-instantiable base blueprints using
abstract class. - We enforced method implementation contracts using
abstractmethods and properties. - We built automated ETL pipelines using the Template Method Design Pattern.
- We evaluated when to use Interfaces vs Abstract Classes.
In Episode 23: Static Members, we will explore class static fields, static methods, and static block initializers (static {})!

