Difficulty: Intermediate
What are abstract classes in TypeScript? How do they differ from interfaces and when should you use each?
Abstract classes are base classes that cannot be instantiated directly. They can contain both implemented methods (shared logic) and abstract methods (contracts subclasses must fulfill).
Key differences from interfaces: - Abstract classes can have implementation (method bodies, property initializers) - Abstract classes can have constructors, access modifiers, and static members - A class can only extend ONE abstract class but implement MANY interfaces - Abstract classes produce runtime JavaScript code; interfaces are erased
Use abstract classes when you have shared implementation logic. Use interfaces when you only need a contract with no shared code.
abstract class Shape {
constructor(public color: string) {}
// Abstract method - must be implemented by subclasses
abstract area(): number;
abstract perimeter(): number;
// Concrete method - shared implementation
describe(): string {
return `A ${this.color} shape with area ${this.area().toFixed(2)}`;
}
}
class Circle extends Shape {
constructor(color: string, public radius: number) {
super(color);
}
area(): number {
return Math.PI * this.radius 2;
}
perimeter(): number {
return 2 * Math.PI * this.radius;
}
}
class Rectangle extends Shape {
constructor(color: string, public width: number, public height: number) {
super(color);
}
area(): number { return this.width * this.height; }
perimeter(): number { return 2 * (this.width + this.height); }
}
// const s = new Shape('red'); // Error: Cannot create instance of abstract class
const c = new Circle('red', 5);
c.describe(); // 'A red shape with area 78.54'
Abstract classes combine contract enforcement (abstract methods) with code reuse (concrete methods). Subclasses must implement all abstract members.
// Interface - pure contract, no implementation
interface Logger {
log(message: string): void;
error(message: string): void;
warn(message: string): void;
}
// Abstract class - contract + shared implementation
abstract class BaseLogger {
abstract log(message: string): void;
// Shared implementation
error(message: string): void {
this.log(`[ERROR] ${message}`);
}
warn(message: string): void {
this.log(`[WARN] ${message}`);
}
// Abstract property
abstract get prefix(): string;
}
class ConsoleLogger extends BaseLogger {
get prefix() { return 'Console'; }
log(message: string): void {
console.log(`[${this.prefix}] ${message}`);
}
}
class FileLogger extends BaseLogger {
get prefix() { return 'File'; }
log(message: string): void {
// fs.appendFileSync('app.log', message + '\n');
}
}
// Both can be used polymorphically
function useLogger(logger: BaseLogger) {
logger.log('Starting app');
logger.warn('Low memory');
logger.error('Crash!');
}
The abstract BaseLogger provides error() and warn() implementations. Subclasses only need to implement log(). An interface would force every class to implement all three methods.
// Abstract class as a template method pattern
abstract class DataProcessor<T, R> {
// Template method - defines the algorithm skeleton
process(input: T): R {
this.validate(input);
const transformed = this.transform(input);
return this.format(transformed);
}
// Hooks for subclasses to implement
abstract validate(input: T): void;
abstract transform(input: T): unknown;
abstract format(data: unknown): R;
}
class CSVProcessor extends DataProcessor<string, string[][]> {
validate(input: string): void {
if (!input.includes(',')) throw new Error('Not valid CSV');
}
transform(input: string): string[][] {
return input.split('\n').map(row => row.split(','));
}
format(data: unknown): string[][] {
return data as string[][];
}
}
class JSONProcessor extends DataProcessor<string, object> {
validate(input: string): void {
try { JSON.parse(input); } catch { throw new Error('Invalid JSON'); }
}
transform(input: string): object {
return JSON.parse(input);
}
format(data: unknown): object {
return data as object;
}
}
The Template Method pattern is a classic use case for abstract classes. The base class defines the algorithm, subclasses provide the steps.
Abstract Classes, Abstract Methods, Inheritance, Polymorphism, Interface vs Abstract