How to fix Cannot make abstract member non-abstract in TypeScript

TypeScriptINTERMEDIATEMEDIUMUpdated Dec 8, 2025· 99 views

This error occurs when a derived class attempts to override an abstract member from a parent abstract class in a way that violates TypeScript's abstraction rules. The fix typically involves properly implementing the abstract member as a concrete method, or understanding inheritance constraints when intermediate abstract classes are involved.

Quick fix

Fixes Cannot make abstract member non-abstract

// Base abstract class
abstract class Animal {
  abstract makeSound(): void; // Must be implemented by concrete classes

  move(): void { // Concrete - has implementation
    console.log("Moving...");
  }
}

// ✅ CORRECT: Concrete class implements abstract member
class Dog extends Animal {
  makeSound(): void { // Implement abstract member
    console.log("Woof!");
  }
}

// ❌ WRONG: Cannot redeclare concrete member as abstract
abstract class IntermediateDog extends Animal {
  makeSound(): void { // This makes it concrete
    console.log("Generic dog sound");
  }
}

// This would cause issues:
abstract class SpecialDog extends IntermediateDog {
  abstract makeSound(): void; // ERROR: Cannot make non-abstract member abstract
}
  1. Understand when members should be abstract vs concrete
  2. Remove abstract keyword from implemented methods in derived classes
  3. Check for conflicting abstract declarations in inheritance chain
  4. Use proper method override syntax without abstract keyword
typescript — error
// Base abstract class
abstract class Animal {
abstract makeSound(): void; // Must be implemented by concrete classes
move(): void { // Concrete - has implementation
console.log("Moving...");
}
}
// ✅ CORRECT: Concrete class implements abstract member
class Dog extends Animal {
makeSound(): void { // Implement abstract member
console.log("Woof!");
}
}
// ❌ WRONG: Cannot redeclare concrete member as abstract
abstract class IntermediateDog extends Animal {
makeSound(): void { // This makes it concrete
console.log("Generic dog sound");
}
}
// This would cause issues:
abstract class SpecialDog extends IntermediateDog {
abstract makeSound(): void; // ERROR: Cannot make non-abstract member abstract
}
Cannot make abstract member non-abstract

What this error means

TypeScript enforces strict rules about abstract members in class inheritance hierarchies. An abstract member is a method or property declared in an abstract class without an implementation, which must be implemented by concrete (non-abstract) derived classes. This error appears in specific inheritance scenarios, particularly when you have multiple levels of abstract classes. If an intermediate abstract class overrides an abstract member from its parent, it cannot change the member from abstract to non-abstract (concrete) if that member is still required to remain abstract in the inheritance chain. The error prevents violations of the abstraction contract defined by the base class. The most common scenario is when you have a chain like: AbstractBase → IntermediateAbstract → ConcreteDerived. If AbstractBase declares an abstract member, IntermediateAbstract can either leave it abstract or implement it. However, certain patterns (like trying to redeclare an abstract member that's already been made concrete, or vice versa) trigger this error.

Typical symptoms

  • TypeScript compilation error when defining derived abstract classes
  • Error message: "Cannot make abstract member non-abstract" in IDE
  • Red squiggly lines under method or property declarations in derived class
  • Build fails when compiling multi-level abstract class hierarchies
  • Error occurs when trying to override abstract members in intermediate abstract classes
  • Confusion about whether to use abstract keyword on overridden members

Common causes

  1. Attempting to redeclare an abstract member as abstract in a class that already implements it
  2. Incorrectly using the abstract keyword on a member that overrides a concrete implementation
  3. Misunderstanding TypeScript's abstract member inheritance rules
  4. Complex inheritance hierarchy with multiple levels of abstract classes
  5. Attempting to make a concrete member abstract again in a derived class
  6. Copy-paste error where abstract keyword was added to implemented methods

How to fix "Cannot make abstract member non-abstract"

1Understand when members should be abstract vs concrete

In TypeScript's class hierarchy, abstract members follow strict rules:

- Abstract classes can have both abstract and concrete members
- Abstract members have no implementation and must be implemented by concrete child classes
- Concrete members have an implementation
- Once a member is implemented (made concrete), derived classes cannot make it abstract again

typescript
// Base abstract class
abstract class Animal {
  abstract makeSound(): void; // Must be implemented by concrete classes

  move(): void { // Concrete - has implementation
    console.log("Moving...");
  }
}

// ✅ CORRECT: Concrete class implements abstract member
class Dog extends Animal {
  makeSound(): void { // Implement abstract member
    console.log("Woof!");
  }
}

// ❌ WRONG: Cannot redeclare concrete member as abstract
abstract class IntermediateDog extends Animal {
  makeSound(): void { // This makes it concrete
    console.log("Generic dog sound");
  }
}

// This would cause issues:
abstract class SpecialDog extends IntermediateDog {
  abstract makeSound(): void; // ERROR: Cannot make non-abstract member abstract
}
2Remove abstract keyword from implemented methods in derived classes

If you're implementing an abstract member from a parent class, don't use the abstract keyword:

typescript
// BEFORE: Error - abstract keyword on implementation
abstract class Shape {
  abstract calculateArea(): number;
}

abstract class Polygon extends Shape {
  abstract calculateArea(): number; // ERROR if calculateArea is implemented
}

// AFTER: Implement without abstract keyword
abstract class Shape {
  abstract calculateArea(): number;
}

abstract class Polygon extends Shape {
  // Option 1: Leave it abstract (don't declare it again)
  abstract getPerimeter(): number; // Add new abstract member
}

// Or implement it as concrete:
abstract class Polygon extends Shape {
  calculateArea(): number { // Concrete implementation
    return 0; // Default or actual implementation
  }
}

class Rectangle extends Polygon {
  width: number;
  height: number;

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

  calculateArea(): number { // Override with specific implementation
    return this.width * this.height;
  }
}
3Check for conflicting abstract declarations in inheritance chain

Review your inheritance hierarchy to ensure abstract members are declared correctly:

typescript
// PROBLEM: Trying to make a concrete member abstract again
abstract class Vehicle {
  start(): void { // Concrete implementation
    console.log("Starting vehicle...");
  }
}

abstract class Car extends Vehicle {
  // ❌ ERROR: Cannot redeclare concrete member as abstract
  abstract start(): void;
}

// SOLUTION 1: Remove the abstract redeclaration
abstract class Car extends Vehicle {
  // Don't redeclare it - inherit the concrete implementation
  // Or override it with a new concrete implementation:
  start(): void {
    console.log("Starting car...");
  }
}

// SOLUTION 2: If you need abstract behavior, use a different pattern
abstract class Vehicle {
  abstract start(): void; // Keep it abstract from the beginning
}

abstract class Car extends Vehicle {
  // Still abstract - child classes must implement
  abstract start(): void; // This is OK - maintaining abstraction
}

class Sedan extends Car {
  start(): void { // Final concrete implementation
    console.log("Sedan starting...");
  }
}
4Use proper method override syntax without abstract keyword

When overriding methods in derived classes, use the override keyword (TypeScript 4.3+) instead of abstract:

typescript
abstract class Database {
  abstract connect(): Promise<void>;
  abstract query(sql: string): Promise<any>;
}

// ✅ CORRECT: Intermediate abstract class with partial implementation
abstract class RelationalDB extends Database {
  // Implement one abstract method
  override async connect(): Promise<void> {
    console.log("Establishing connection...");
  }

  // Leave query abstract for child classes
  // (Don't redeclare it - it stays abstract)
}

// Final concrete class
class PostgresDB extends RelationalDB {
  override async query(sql: string): Promise<any> {
    console.log(`Executing: ${sql}`);
    return [];
  }
}

// ❌ WRONG: Using abstract when implementing
abstract class RelationalDB extends Database {
  abstract async connect(): Promise<void> { // ERROR: abstract with implementation
    console.log("Establishing connection...");
  }
}
5Simplify your class hierarchy if needed

Complex multi-level abstract hierarchies can lead to confusion. Consider simplifying:

typescript
// BEFORE: Complex multi-level abstraction
abstract class Base {
  abstract method1(): void;
  abstract method2(): void;
}

abstract class Middle extends Base {
  abstract method1(): void; // Redundant - already abstract
  method2(): void { // Implemented here
    console.log("Method 2");
  }
}

abstract class AlmostConcrete extends Middle {
  method1(): void { // Implement method1
    console.log("Method 1");
  }
}

// AFTER: Simplified and clearer
abstract class Base {
  abstract method1(): void;
  abstract method2(): void;
}

// Skip unnecessary intermediate abstractions
class Concrete extends Base {
  method1(): void {
    console.log("Method 1");
  }

  method2(): void {
    console.log("Method 2");
  }
}

// Or use composition instead of deep inheritance
interface IMethod1 {
  method1(): void;
}

interface IMethod2 {
  method2(): void;
}

class Concrete implements IMethod1, IMethod2 {
  method1(): void {
    console.log("Method 1");
  }

  method2(): void {
    console.log("Method 2");
  }
}
6Use interfaces for pure contracts instead of abstract classes

If you're not sharing implementation code, interfaces are clearer and more flexible:

typescript
// BEFORE: Abstract class just for contracts
abstract class Repository {
  abstract find(id: string): Promise<any>;
  abstract save(entity: any): Promise<void>;
  abstract delete(id: string): Promise<void>;
}

// Can lead to issues with multi-level abstractions

// AFTER: Interface for pure contracts
interface Repository {
  find(id: string): Promise<any>;
  save(entity: any): Promise<void>;
  delete(id: string): Promise<void>;
}

// Implement directly
class UserRepository implements Repository {
  async find(id: string): Promise<any> {
    return { id, name: "User" };
  }

  async save(entity: any): Promise<void> {
    console.log("Saving user");
  }

  async delete(id: string): Promise<void> {
    console.log("Deleting user");
  }
}

// Can implement multiple interfaces without inheritance complexity
interface Cacheable {
  clearCache(): void;
}

class CachedUserRepository implements Repository, Cacheable {
  async find(id: string): Promise<any> {
    return { id, name: "User" };
  }

  async save(entity: any): Promise<void> {
    console.log("Saving user");
  }

  async delete(id: string): Promise<void> {
    console.log("Deleting user");
  }

  clearCache(): void {
    console.log("Cache cleared");
  }
}

Advanced notes

### Abstract Member Inheritance Rules

TypeScript enforces these strict rules for abstract members:

1. Abstract to Abstract: An abstract class can inherit an abstract member and keep it abstract (no redeclaration needed)
2. Abstract to Concrete: An abstract class can implement an abstract member from its parent
3. Concrete to Concrete: A class can override a concrete member with another concrete implementation
4. Concrete to Abstract: ❌ NOT ALLOWED - once implemented, cannot be made abstract again

### Why This Error Exists

This restriction ensures type safety and prevents breaking the Liskov Substitution Principle. If a parent class provides a concrete implementation, child classes can rely on that implementation existing. Allowing a child to make it abstract again would break this guarantee.

### Template Method Pattern

Abstract classes shine in the Template Method pattern, where the base class defines an algorithm skeleton and child classes implement specific steps:

typescript
abstract class DataProcessor {
  // Template method (concrete)
  process(data: any[]): void {
    this.validate(data);
    const transformed = this.transform(data);
    this.save(transformed);
  }

  // Concrete step with default implementation
  validate(data: any[]): void {
    if (!Array.isArray(data)) {
      throw new Error("Data must be an array");
    }
  }

  // Abstract steps to be implemented by child classes
  abstract transform(data: any[]): any[];
  abstract save(data: any[]): void;
}

class CSVProcessor extends DataProcessor {
  transform(data: any[]): any[] {
    return data.map(row => row.split(','));
  }

  save(data: any[]): void {
    console.log("Saving to CSV file");
  }
}

### Mixing Abstract Classes and Interfaces

For maximum flexibility, combine abstract classes (for shared implementation) with interfaces (for contracts):

typescript
// Interface defines the contract
interface Logger {
  log(message: string): void;
  error(message: string): void;
}

// Abstract class provides partial implementation
abstract class BaseService implements Logger {
  // Concrete implementation of Logger interface
  log(message: string): void {
    console.log(`[${this.constructor.name}] ${message}`);
  }

  error(message: string): void {
    console.error(`[${this.constructor.name}] ERROR: ${message}`);
  }

  // Abstract method for child classes
  abstract execute(): Promise<void>;
}

// Concrete implementation
class UserService extends BaseService {
  async execute(): Promise<void> {
    this.log("Executing user service");
    // Business logic here
  }
}

### When to Use Multi-Level Abstract Classes

Multiple levels of abstract classes are appropriate when:

- You have a family of related classes with progressive specialization
- Each level adds new abstract requirements
- Intermediate levels provide partial implementations
- The domain naturally models as a deep hierarchy (e.g., Entity → LivingEntity → Animal → Mammal)

However, favor composition over deep inheritance when possible. Interfaces with helper utilities often provide better flexibility.

Frequently asked questions

What does "Cannot make abstract member non-abstract" mean?
TypeScript enforces strict rules about abstract members in class inheritance hierarchies. An abstract member is a method or property declared in an abstract class without an implementation, which must be implemented by concrete (non-abstract) derived classes. This error appears in specific inheritance scenarios, particularly when you have multiple levels of abstract classes.
How do I fix "Cannot make abstract member non-abstract"?
This error occurs when a derived class attempts to override an abstract member from a parent abstract class in a way that violates TypeScript's abstraction rules. The fix typically involves properly implementing the abstract member as a concrete method, or understanding inheritance constraints when intermediate abstract classes are involved.
What causes "Cannot make abstract member non-abstract"?
Attempting to redeclare an abstract member as abstract in a class that already implements it Incorrectly using the abstract keyword on a member that overrides a concrete implementation Misunderstanding TypeScript's abstract member inheritance rules
Is the "Cannot make abstract member non-abstract" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading