Skip to main content
Practice Problems

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

FeatureAbstract ClassInterface
ImplementationCan have implemented methodsNo implementation
ConstructorYesNo
Access modifiersYes (public, private, protected)No
Properties with valuesYesNo
Multiple inheritanceNo (single extends)Yes (multiple implements)
Runtime existenceYes (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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems