How to fix Function return type must be assignable to base class return type 'X' in TypeScript

TypeScriptINTERMEDIATEHIGHUpdated Dec 10, 2025ยท 113 views

This TypeScript error occurs when overriding a method in a subclass where the return type is not compatible with the base class method's return type. It's a type safety check that ensures method overrides maintain type compatibility, preventing runtime errors from incompatible return types. Understanding covariance and type compatibility rules resolves this object-oriented programming issue.

Quick fix

Fixes TS2416

class Animal {
  makeSound(): string {
    return "generic animal sound";
  }
}

class Dog extends Animal {
  // This must return something assignable to string
  makeSound(): string {
    return "bark";
  }
}
  1. Understand the base class return type requirement
  2. Ensure subclass return type is a subtype of base class return type
  3. Check for union type compatibility
  4. Handle generic method overrides correctly
typescript โ€” error
class Animal {
makeSound(): string {
return "generic animal sound";
}
}
class Dog extends Animal {
// This must return something assignable to string
makeSound(): string {
return "bark";
}
}
Function return type must be assignable to base class return type 'X'

What this error means

This error occurs when you override a method in a subclass and the return type you specify is not compatible with the return type declared in the base class. TypeScript enforces that method overrides must be type-safe: the return type in the subclass must be assignable to (compatible with) the return type in the base class. This is known as covariance for return types. The error prevents situations where code expecting the base class return type receives an incompatible type from the subclass method, which could cause runtime errors. For example, if a base class method returns `Animal`, a subclass method can return `Dog` (since Dog is a subtype of Animal), but not `Vehicle` (which is unrelated). This ensures the Liskov Substitution Principle is maintained in your object-oriented design.

Typical symptoms

  • TypeScript compiler shows error "TS2416: Property 'methodName' in type 'SubClass' is not assignable to the same property in base type 'BaseClass'"
  • The error message includes "Function return type must be assignable to base class return type 'X'"
  • Method overrides in subclasses are highlighted in red in your IDE
  • Build process fails when running `npm run build` or `tsc`
  • The error points to a specific method declaration in the subclass
  • Related types (actual vs expected) are shown in the error message
  • Error appears when extending classes or implementing interfaces

Common causes

  1. Returning a completely unrelated type in the subclass method (e.g., base returns `string`, subclass returns `number`)
  2. Returning a type that is not a subtype of the base class return type
  3. Using a more restrictive return type that cannot satisfy the base class contract
  4. Forgetting to specify the correct return type annotation in the subclass method
  5. Incorrectly narrowing the return type beyond what the base class allows
  6. Implementing an interface method with an incompatible return type
  7. Using union types where the subclass return type is not a subset of the base class return type
  8. Generic type mismatches when overriding generic methods
  9. Returning `null` or `undefined` when the base class return type doesn't allow them
  10. Using conditional types or mapped types that produce incompatible return types

How to fix "Function return type must be assignable to base class return type 'X'"

1Understand the base class return type requirement

First, examine the base class or interface to understand what return type is expected. The error message shows the expected type (X). You need to ensure your subclass method returns a type that is assignable to this type.

typescript
class Animal {
  makeSound(): string {
    return "generic animal sound";
  }
}

class Dog extends Animal {
  // This must return something assignable to string
  makeSound(): string {
    return "bark";
  }
}
2Ensure subclass return type is a subtype of base class return type

The subclass method can return a more specific type (subtype) but not a completely different type. This is covariance: you can return a Dog where Animal is expected, but not a Car.

typescript
class Animal {}
class Dog extends Animal {}
class Car {}

class AnimalHandler {
  getAnimal(): Animal {
    return new Animal();
  }
}

class DogHandler extends AnimalHandler {
  // OK: Dog is a subtype of Animal
  getAnimal(): Dog {
    return new Dog();
  }
}

class CarHandler extends AnimalHandler {
  // ERROR: Car is not a subtype of Animal
  getAnimal(): Car {
    return new Car();
  }
}
3Check for union type compatibility

If the base class returns a union type, the subclass must return a type that is assignable to that union. The subclass can return a subset of the union or a single type from the union.

typescript
class Base {
  getValue(): string | number {
    return "hello";
  }
}

class Sub1 extends Base {
  // OK: string is assignable to string | number
  getValue(): string {
    return "subclass value";
  }
}

class Sub2 extends Base {
  // OK: number is assignable to string | number
  getValue(): number {
    return 42;
  }
}

class Sub3 extends Base {
  // ERROR: boolean is not assignable to string | number
  getValue(): boolean {
    return true;
  }
}
4Handle generic method overrides correctly

When overriding generic methods, ensure type parameters are compatible. The subclass can use the same or more specific type parameters, but not completely different ones.

typescript
class Base<T> {
  process(value: T): T {
    return value;
  }
}

class Sub1 extends Base<string> {
  // OK: same type parameter
  process(value: string): string {
    return value.toUpperCase();
  }
}

class Sub2 extends Base<Animal> {
  // OK: returning Dog (subtype of Animal) where Animal is expected
  process(value: Animal): Dog {
    return new Dog();
  }
}

class Sub3 extends Base<number> {
  // ERROR: string is not assignable to number
  process(value: number): string {
    return value.toString();
  }
}
5Use return type widening when appropriate

Sometimes you need to widen the return type in the subclass to match the base class. This might mean returning a less specific type than you'd prefer, but it maintains type safety.

typescript
interface Shape {
  area(): number;
}

class Circle implements Shape {
  radius: number;
  
  // Must return number to satisfy Shape interface
  area(): number {
    return Math.PI * this.radius * this.radius;
  }
}

// If you try to return something more specific:
class Square implements Shape {
  side: number;
  
  // ERROR: {value: number, unit: string} is not assignable to number
  area(): {value: number, unit: string} {
    return {value: this.side * this.side, unit: "cmยฒ"};
  }
  
  // CORRECT: return number
  area(): number {
    return this.side * this.side;
  }
}
6Check interface implementation return types

When implementing an interface, ensure your method return types match exactly or are subtypes of the interface return types.

typescript
interface Repository<T> {
  findById(id: string): T | null;
}

class UserRepository implements Repository<User> {
  // OK: User | null is assignable to User | null
  findById(id: string): User | null {
    // implementation
    return null;
  }
}

class ProductRepository implements Repository<Product> {
  // ERROR: Product is not assignable to Product | null
  // (missing null in return type)
  findById(id: string): Product {
    // implementation
    return new Product();
  }
  
  // CORRECT: include null
  findById(id: string): Product | null {
    // implementation
    return new Product();
  }
}
7Use method overloads for complex return type scenarios

If you need to return different types based on conditions, consider using method overloads. However, the implementation signature must be compatible with all overloads.

typescript
abstract class Parser {
  abstract parse(input: string): any;
}

class JsonParser extends Parser {
  // Using overloads for more specific return types
  parse(input: string): object;
  parse(input: string): any {
    try {
      return JSON.parse(input);
    } catch {
      return null;
    }
  }
}

// The implementation returns any, which is assignable to any
// The overload provides more specific type information to callers
8Review TypeScript compiler settings for strictness

Some TypeScript compiler flags affect method override checking. The strict flag enables strict checking of method overrides. If you're migrating from JavaScript or a less strict TypeScript configuration, you might need to adjust your code.

json
{
  "compilerOptions": {
    "strict": true,  // Enables strict method override checking
    "noImplicitOverride": true  // Requires explicit `override` keyword
  }
}

With noImplicitOverride, you must use the override keyword:

typescript
class Base {
  method(): string {
    return "base";
  }
}

class Derived extends Base {
  override method(): string {
    return "derived";
  }
}

Advanced notes

TypeScript uses structural typing for most type compatibility, but for method overrides it follows specific rules. Return types are covariant: a subclass method can return a subtype of the base class return type. This is different from parameter types, which are contravariant (when strictFunctionTypes is enabled). The override keyword (available in TypeScript 4.3+) helps catch errors by requiring explicit annotation of method overrides. When working with generic classes, remember that class Sub extends Base<SpecificType> creates a new class hierarchy; the subclass methods must be compatible with the instantiated base class. For abstract classes, concrete subclasses must implement all abstract methods with compatible return types. When using conditional types like T extends U ? X : Y in return types, ensure all possible branches are assignable to the base return type. The ReturnType<T> utility type can help extract return types for comparison. Remember that void is a special return type: a method returning void can be overridden by a method returning any type (except undefined with strict settings), but the return value will be ignored.

Frequently asked questions

What does "TS2416" mean?
This error occurs when you override a method in a subclass and the return type you specify is not compatible with the return type declared in the base class. TypeScript enforces that method overrides must be type-safe: the return type in the subclass must be assignable to (compatible with) the return type in the base class. This is known as covariance for return types.
How do I fix "TS2416"?
This TypeScript error occurs when overriding a method in a subclass where the return type is not compatible with the base class method's return type. It's a type safety check that ensures method overrides maintain type compatibility, preventing runtime errors from incompatible return types. Understanding covariance and type compatibility rules resolves this object-oriented programming issue.
What causes "TS2416"?
Returning a completely unrelated type in the subclass method (e.g., base returns string, subclass returns number) Returning a type that is not a subtype of the base class return type Using a more restrictive return type that cannot satisfy the base class contract
Is the "TS2416" error serious?
Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading