How to fix Cannot extend non-abstract class with abstract members in TypeScript

TypeScriptINTERMEDIATEMEDIUMUpdated Dec 8, 2025· 98 views

This TypeScript error occurs when a non-abstract (concrete) class extends an abstract base class but fails to implement all abstract members. All classes that extend abstract classes must either implement every abstract method and property, or be declared abstract themselves.

Quick fix

Fixes TS2515

// Example error message:
// Non-abstract class 'Dog' does not implement inherited
// abstract member 'makeSound' from class 'Animal'

abstract class Animal {
  abstract makeSound(): string;
  abstract age: number;
}

// This will cause the error:
class Dog extends Animal {
  // Missing: makeSound() and age
}
  1. Identify which abstract members are missing
  2. Implement all abstract methods
  3. Implement abstract properties
  4. Verify method signatures match exactly
typescript — error
// Example error message:
// Non-abstract class 'Dog' does not implement inherited
// abstract member 'makeSound' from class 'Animal'
abstract class Animal {
abstract makeSound(): string;
abstract age: number;
}
// This will cause the error:
class Dog extends Animal {
// Missing: makeSound() and age
}
Cannot extend non-abstract class with abstract members

What this error means

The "Cannot extend non-abstract class with abstract members" error appears when you create a regular class that extends an abstract class, but you don't provide implementations for all the abstract methods or properties defined in the base class. Abstract classes in TypeScript serve as blueprints that define a contract. When you mark a method or property as abstract, you're declaring that any concrete (non-abstract) subclass must provide its own implementation. If a class extends an abstract class without implementing all abstract members, TypeScript raises this error to enforce the contract. This is a fundamental principle of object-oriented programming: abstract members exist to ensure that all concrete instances have the required functionality. TypeScript enforces this at compile time, preventing runtime errors where you might try to call a method that doesn't exist. The error typically manifests as "Non-abstract class 'ClassName' does not implement inherited abstract member 'methodName' from class 'BaseClassName'." This clearly identifies which abstract members are missing implementations.

Typical symptoms

  • Compilation fails with 'Non-abstract class does not implement inherited abstract member'
  • Error code TS2515 appears in TypeScript output
  • IDE shows red underlines on the class declaration extending the abstract class
  • Error message specifically names the missing abstract method or property
  • Code worked in JavaScript but breaks when adding TypeScript types
  • Multiple errors if several abstract members are unimplemented

Common causes

  1. Forgot to implement one or more abstract methods from the parent class
  2. Implemented the method but with incorrect signature (wrong name, parameters, or return type)
  3. Typo in method name - implementation doesn't match the abstract declaration exactly
  4. Class extends abstract base but was not meant to be concrete yet
  5. Abstract property declared in base class but not assigned in derived class
  6. Added new abstract member to base class but didn't update all subclasses
  7. Case sensitivity mismatch in method names between declaration and implementation

How to fix "Cannot extend non-abstract class with abstract members"

1Identify which abstract members are missing

Read the full error message to see exactly which members need implementation:

typescript
// Example error message:
// Non-abstract class 'Dog' does not implement inherited
// abstract member 'makeSound' from class 'Animal'

abstract class Animal {
  abstract makeSound(): string;
  abstract age: number;
}

// This will cause the error:
class Dog extends Animal {
  // Missing: makeSound() and age
}

TypeScript will list all missing members. Note them down before proceeding to fix.

2Implement all abstract methods

Add implementations for each abstract method with the exact signature:

typescript
abstract class Animal {
  abstract makeSound(): string;
  abstract move(distance: number): void;
}

// CORRECT - All abstract methods implemented
class Dog extends Animal {
  makeSound(): string {
    return "Woof!";
  }

  move(distance: number): void {
    console.log(`Dog moved ${distance} meters`);
  }
}

// Now you can instantiate:
const dog = new Dog();
console.log(dog.makeSound()); // "Woof!"
dog.move(10);

Ensure:
- Method names match exactly (case-sensitive)
- Parameter types match the abstract declaration
- Return types match or are compatible
- Access modifiers are compatible (can't make public method private)

3Implement abstract properties

Abstract properties must also be defined in the concrete class:

typescript
abstract class Vehicle {
  abstract wheels: number;
  abstract readonly brand: string;
}

// CORRECT implementation
class Car extends Vehicle {
  wheels: number = 4;
  readonly brand: string = "Toyota";

  // Or initialize in constructor:
  constructor(brand: string) {
    super();
    this.brand = brand;
  }
}

// WRONG - missing property implementations
class Motorcycle extends Vehicle {
  wheels: number = 2;
  // Error: missing 'brand' property
}

Properties can be initialized:
- Directly in the class body
- In the constructor
- As constructor parameters with access modifiers

4Verify method signatures match exactly

If you think you implemented the method but still get the error, check the signature:

typescript
abstract class Shape {
  abstract calculateArea(): number;
  abstract draw(context: CanvasRenderingContext2D): void;
}

// WRONG - signature mismatch
class Circle extends Shape {
  // Wrong return type (void instead of number)
  calculateArea(): void {
    console.log("Calculating...");
  }

  // Wrong parameter type
  draw(context: any): void {
    // ...
  }
}

// CORRECT - signatures match
class Rectangle extends Shape {
  width: number;
  height: number;

  constructor(width: number, height: number) {
    super();
    this.width = width;
    this.height = height;
  }

  calculateArea(): number {
    return this.width * this.height;
  }

  draw(context: CanvasRenderingContext2D): void {
    context.fillRect(0, 0, this.width, this.height);
  }
}

Use your IDE's autocomplete after typing "override" to ensure correct signatures.

5Make the derived class abstract if not ready to implement

If you're building a class hierarchy and aren't ready to implement all members, mark the derived class as abstract too:

typescript
abstract class Animal {
  abstract makeSound(): string;
  abstract eat(food: string): void;
}

// Intermediate abstract class - doesn't implement everything
abstract class Mammal extends Animal {
  // Implements only one abstract member
  eat(food: string): void {
    console.log(`Eating ${food}`);
  }

  // makeSound() still abstract - concrete subclasses must implement
}

// Now concrete class only needs to implement remaining abstract members
class Cat extends Mammal {
  makeSound(): string {
    return "Meow";
  }
  // eat() is inherited from Mammal
}

const cat = new Cat();
cat.eat("fish");           // From Mammal
console.log(cat.makeSound()); // From Cat

This is useful for creating layered class hierarchies where some behavior is shared across a group of subclasses.

6Use the override keyword for clarity

TypeScript 4.3+ supports the override keyword to make implementation intent explicit:

typescript
abstract class Base {
  abstract process(): void;

  // Non-abstract method
  initialize(): void {
    console.log("Initializing...");
  }
}

class Derived extends Base {
  // Implementing abstract method
  override process(): void {
    console.log("Processing...");
  }

  // Overriding non-abstract method
  override initialize(): void {
    super.initialize();
    console.log("Extra initialization");
  }
}

Enable in tsconfig.json:

json
{
  "compilerOptions": {
    "noImplicitOverride": true
  }
}

This makes TypeScript require override for all overridden members, catching typos where you think you're implementing an abstract member but actually created a new method.

Advanced notes

### Abstract Classes vs Interfaces

Abstract classes and interfaces both define contracts, but have key differences:

Abstract classes:
- Can have both abstract and concrete members
- Can have constructors, fields, and implementation logic
- Support access modifiers (private, protected, public)
- Single inheritance only (a class can extend one abstract class)

typescript
abstract class AbstractLogger {
  protected logLevel: string = "info";

  abstract log(message: string): void;

  // Concrete helper method
  formatMessage(msg: string): string {
    return `[${this.logLevel}] ${msg}`;
  }
}

Interfaces:
- Only define method/property signatures
- No implementation or state
- Multiple inheritance (a class can implement many interfaces)
- All members are implicitly public

typescript
interface ILogger {
  log(message: string): void;
  level: string;
}

When to use abstract classes:
- You need to share implementation code across subclasses
- You need to maintain state or have constructor logic
- You want to use protected/private members
- You're modeling "is-a" relationships

When to use interfaces:
- You need multiple inheritance
- You're defining a pure contract with no implementation
- You're modeling "can-do" capabilities
- You want maximum flexibility for implementers

### Partial Implementation Pattern

You can combine abstract classes with partial implementation:

typescript
abstract class DataStore {
  // Abstract - must be implemented
  abstract connect(): Promise<void>;
  abstract disconnect(): Promise<void>;
  abstract query(sql: string): Promise<any>;

  // Concrete - shared across all implementations
  private isConnected: boolean = false;

  async ensureConnected(): Promise<void> {
    if (!this.isConnected) {
      await this.connect();
      this.isConnected = true;
    }
  }

  // Template method pattern
  async executeQuery(sql: string): Promise<any> {
    await this.ensureConnected();
    return this.query(sql);
  }
}

class PostgresStore extends DataStore {
  async connect(): Promise<void> {
    // Postgres-specific connection
  }

  async disconnect(): Promise<void> {
    // Postgres-specific disconnection
  }

  async query(sql: string): Promise<any> {
    // Postgres-specific query
  }

  // Inherits ensureConnected() and executeQuery()
}

This pattern (Template Method) lets you define the algorithm structure in the abstract class while letting subclasses customize specific steps.

### Abstract Static Members

TypeScript 4.9+ introduced abstract static members:

typescript
abstract class Model {
  abstract id: number;

  // Abstract static method
  static abstract fromJSON(data: any): Model;

  // Regular abstract instance method
  abstract save(): Promise<void>;
}

class User extends Model {
  id: number;
  name: string;

  constructor(id: number, name: string) {
    super();
    this.id = id;
    this.name = name;
  }

  // Must implement static method
  static fromJSON(data: any): User {
    return new User(data.id, data.name);
  }

  // Must implement instance method
  async save(): Promise<void> {
    // Save logic
  }
}

// Usage:
const user = User.fromJSON({ id: 1, name: "Alice" });
await user.save();

This enforces that all subclasses implement the same static factory methods or utilities.

### Constructor Signatures in Abstract Classes

Abstract classes can define constructor signatures that subclasses must follow:

typescript
abstract class Component {
  protected props: any;

  constructor(props: any) {
    this.props = props;
  }

  abstract render(): HTMLElement;
}

class Button extends Component {
  // Must call super() with props
  constructor(props: any) {
    super(props);
  }

  render(): HTMLElement {
    const button = document.createElement("button");
    button.textContent = this.props.label;
    return button;
  }
}

Subclass constructors must call super() before accessing this.

### Type-Safe Abstract Factory Pattern

Combine abstract classes with generics for type-safe factories:

typescript
abstract class Factory<T> {
  abstract create(): T;

  createMultiple(count: number): T[] {
    return Array(count).fill(null).map(() => this.create());
  }
}

class UserFactory extends Factory<User> {
  create(): User {
    return new User(
      Math.random(),
      `User${Math.floor(Math.random() * 1000)}`
    );
  }
}

const factory = new UserFactory();
const users: User[] = factory.createMultiple(5);

This ensures type safety across the factory hierarchy.

Frequently asked questions

What does "TS2515" mean?
The "Cannot extend non-abstract class with abstract members" error appears when you create a regular class that extends an abstract class, but you don't provide implementations for all the abstract methods or properties defined in the base class. Abstract classes in TypeScript serve as blueprints that define a contract. When you mark a method or property as abstract, you're declaring that any concrete (non-abstract) subclass must provide its own implementation.
How do I fix "TS2515"?
This TypeScript error occurs when a non-abstract (concrete) class extends an abstract base class but fails to implement all abstract members. All classes that extend abstract classes must either implement every abstract method and property, or be declared abstract themselves.
What causes "TS2515"?
Forgot to implement one or more abstract methods from the parent class Implemented the method but with incorrect signature (wrong name, parameters, or return type) Typo in method name - implementation doesn't match the abstract declaration exactly
Is the "TS2515" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading