Abstract classes in TypeScript
What are Abstract Classes?
An abstract class is a class that cannot be instantiated directly β it serves as a base class for other classes. It can contain both implemented methods and abstract methods (methods without implementation that subclasses must implement).
Basic Syntax
typescript
abstract class Shape {
// Abstract method β no implementation, must be overridden
abstract area(): number;
abstract perimeter(): number;
// Concrete method β has implementation, inherited by subclasses
describe(): string {
return `Area: ${this.area()}, Perimeter: ${this.perimeter()}`;
}
}
// β Cannot instantiate abstract class
const shape = new Shape(); // Error!
// β
Must extend and implement abstract methods
class Circle extends Shape {
constructor(private radius: number) {
super();
}
area(): number {
return Math.PI * this.radius ** 2;
}
perimeter(): number {
return 2 * Math.PI * this.radius;
}
}
const circle = new Circle(5);
circle.area(); // 78.54
circle.describe(); // "Area: 78.54, Perimeter: 31.42"Abstract vs Interface
| Feature | Abstract Class | Interface |
|---|---|---|
| Implementation | Can have implemented methods | No implementation |
| Constructor | Yes | No |
| Access modifiers | Yes (public, private, protected) | No |
| Properties with values | Yes | No |
| Multiple inheritance | No (single extends) | Yes (multiple implements) |
| Runtime existence | Yes (transpiles to JS class) | No (erased at compile time) |
typescript
// Interface β pure contract
interface Printable {
print(): void;
}
// Abstract class β contract + shared logic
abstract class BaseRepository<T> {
protected items: T[] = [];
abstract findById(id: string): T | undefined;
getAll(): T[] {
return [...this.items];
}
count(): number {
return this.items.length;
}
}Practical Use Cases
Repository Pattern
typescript
abstract class BaseRepository<T extends { id: string }> {
protected abstract collection: string;
abstract save(entity: T): Promise<T>;
abstract delete(id: string): Promise<void>;
async findById(id: string): Promise<T | null> {
// Shared implementation using this.collection
const response = await fetch(`/api/${this.collection}/${id}`);
return response.json();
}
}
class UserRepository extends BaseRepository<User> {
protected collection = "users";
async save(user: User): Promise<User> {
// User-specific save logic
return user;
}
async delete(id: string): Promise<void> {
// User-specific delete logic
}
}Abstract Properties
typescript
abstract class Component {
abstract readonly tagName: string;
abstract render(): string;
mount(container: HTMLElement): void {
container.innerHTML = this.render();
}
}
class Button extends Component {
readonly tagName = "button";
render(): string {
return `<${this.tagName}>Click me</${this.tagName}>`;
}
}When to Use Abstract Classes
- Shared behavior β common logic that all subclasses need
- Template method pattern β define algorithm structure, let subclasses fill in steps
- State with behavior β when you need both properties and methods
- Single hierarchy β when classes naturally form a tree
When to Use Interfaces Instead
- Multiple contracts β a class needs to implement several contracts
- Pure shape β just defining a data structure
- No shared logic β no common implementation to share
- Decoupling β loose coupling between modules
Important:
Use abstract classes when subclasses share common implementation logic. Use interfaces when you only need to define a contract (shape). In practice, you'll often use both: abstract classes for inheritance hierarchies and interfaces for capability contracts.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.