This TypeScript error occurs when a method override or implementation has a return type that doesn't match the parent class or interface definition. It ensures type safety when extending classes or implementing interfaces by enforcing consistent return types across the inheritance hierarchy. Understanding method signature compatibility and proper type annotations resolves this object-oriented programming error.
This TypeScript error occurs when you override a method in a subclass or implement an interface method with an incompatible return type. TypeScript enforces strict type checking for method overrides to ensure that subclasses maintain the contract defined by their parent class or interface. The "base signature" refers to the method declaration in the parent class or interface, and your override must have a return type that is assignable to (compatible with) that base return type. This is part of TypeScript's type safety system for object-oriented programming, preventing subtle bugs where a subclass method might return a different type than what callers of the parent class method expect. The error ensures that polymorphism works correctly: code that works with a parent class reference can safely call methods and expect consistent return types, even when the actual object is an instance of a subclass.
First, locate both the base method (in parent class or interface) and your overriding method. Compare their return types carefully. The error message will show both types.
// Base class with method signature
class Animal {
makeSound(): string {
return "generic animal sound";
}
}
// Subclass with incompatible override
class Dog extends Animal {
// ERROR: The return type does not match the base signature
makeSound(): number {
return 42; // returning number instead of string
}
}The overriding method's return type must be assignable to (compatible with) the base return type. For objects, this usually means the return type must have all the properties of the base type (structural typing). For primitives, the types must match exactly or be compatible through type relationships.
// Base interface
interface Shape {
getArea(): number;
}
// Correct implementation - same return type
class Circle implements Shape {
getArea(): number {
return Math.PI * this.radius * this.radius;
}
}
// Also correct - returning a subtype (number literal type)
class Square implements Shape {
getArea(): 100 { // 100 is assignable to number
return 100;
}
}
// ERROR - string not assignable to number
class Triangle implements Shape {
getArea(): string {
return "100";
}
}When overriding async methods or methods returning Promises, ensure the Promise generic type matches. An async method that returns Promise<string> cannot be overridden with a method returning Promise<number>.
abstract class DataFetcher {
abstract fetchData(): Promise<string>;
}
// Correct - same Promise type
class ApiFetcher extends DataFetcher {
async fetchData(): Promise<string> {
const response = await fetch("/api/data");
return response.text();
}
}
// ERROR - Promise<number> not assignable to Promise<string>
class NumberFetcher extends DataFetcher {
async fetchData(): Promise<number> {
return 42;
}
}
// Also correct - returning a subtype
class JsonFetcher extends DataFetcher {
async fetchData(): Promise<string> {
const response = await fetch("/api/json");
const data = await response.json();
return JSON.stringify(data); // returns string
}
}TypeScript allows covariant return types in method overrides - you can return a more specific (subtype) of the base return type. This is often useful and type-safe.
class Vehicle {
getEngine(): Engine {
return new Engine();
}
}
class Car extends Vehicle {
// CORRECT - TurboEngine extends Engine, so this is covariant
getEngine(): TurboEngine {
return new TurboEngine();
}
}
class Engine {}
class TurboEngine extends Engine {}
// This works because TurboEngine can be used wherever Engine is expected
const car = new Car();
const engine: Engine = car.getEngine(); // TurboEngine is assignable to EngineIf the base method uses generic type parameters, your override must handle them correctly. You can use the same generics or provide specific types that satisfy the constraints.
interface Repository<T> {
findById(id: string): T | null;
}
// Correct - same generic type
class UserRepository implements Repository<User> {
findById(id: string): User | null {
// implementation
return null;
}
}
// ERROR - return type doesn't match
class ProductRepository implements Repository<Product> {
findById(id: string): string | null { // string not assignable to Product
return "product data";
}
}
// For generic methods within classes
class Base {
process<T>(value: T): T {
return value;
}
}
class Derived extends Base {
// Can override with same signature
process<T>(value: T): T {
return super.process(value);
}
// ERROR - different return type
process<T>(value: T): string {
return "converted";
}
}Methods returning void in the base class can be overridden with methods that return any type (the return value is ignored). However, methods with non-void return types cannot be overridden with void methods.
class EventEmitter {
// Base method returns void
onEvent(): void {
console.log("event");
}
}
class MyEmitter extends EventEmitter {
// CORRECT - can return a value even though base returns void
onEvent(): string {
console.log("my event");
return "handled"; // This is allowed!
}
}
class Reader {
// Base method returns string
read(): string {
return "data";
}
}
class MyReader extends Reader {
// ERROR - void not assignable to string
read(): void {
console.log("reading");
}
}When overriding methods, explicitly annotate the return type rather than relying on TypeScript inference. This makes your intent clear and helps catch errors early.
interface Calculator {
calculate(x: number, y: number): number;
}
// Without explicit annotation, TypeScript might infer wrong type
class BadCalculator implements Calculator {
calculate(x: number, y: number) {
// TypeScript infers return type as string here
return (x + y).toString(); // ERROR: string not assignable to number
}
}
// With explicit annotation, error is clearer
class GoodCalculator implements Calculator {
calculate(x: number, y: number): number { // Explicit return type
return x + y; // Correct
}
}If the base method has overloads or returns a union type, ensure your override satisfies all possible cases. The override must be assignable to all base signatures.
// Base with overloads
class Parser {
parse(input: string): string;
parse(input: number): number;
parse(input: string | number): string | number {
return input;
}
}
// Override must handle all cases
class MyParser extends Parser {
// ERROR - doesn't match all overloads
parse(input: string): string {
return input.toUpperCase();
}
}
// Correct - matches all overloads
class CorrectParser extends Parser {
parse(input: string): string;
parse(input: number): number;
parse(input: string | number): string | number {
if (typeof input === "string") {
return input.toUpperCase();
} else {
return input * 2;
}
}
}TypeScript's method override checking follows the Liskov Substitution Principle (LSP), which states that objects of a superclass should be replaceable with objects of its subclasses without breaking the program. For return types, this means covariant return types are allowed (returning a subtype) because a more specific type can be used wherever a more general type is expected. However, TypeScript is stricter than some other languages: it requires explicit compatibility. The strictFunctionTypes compiler option affects method parameter checking (contravariance) but not return type checking. When working with abstract classes and interfaces, remember that all abstract methods must be implemented with compatible return types. For generic classes and methods, type parameter constraints must be satisfied. The override keyword (available in TypeScript 4.3+) can help catch errors by requiring explicit annotation of method overrides. When dealing with complex inheritance hierarchies, consider using composition over inheritance or defining more flexible type signatures with union types or generic constraints.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript