This TypeScript error occurs when a class or interface extends another type but has incompatible property types. It's a type safety error that prevents inheritance violations where derived types must be assignable to their base types. Understanding type compatibility in inheritance hierarchies resolves this common object-oriented programming issue.
This error indicates a type mismatch between a property in a derived class/interface and the same property in its base type. When a type extends another type (through inheritance or interface extension), all properties in the derived type must be assignable to their corresponding properties in the base type. This is a fundamental principle of type safety in object-oriented programming: a derived type should be usable wherever the base type is expected (Liskov Substitution Principle). The error occurs when you try to override a property with an incompatible type, making the derived type not assignable to the base type. For example, if a base class has a property of type `string`, a derived class cannot change it to `number` because code expecting the base type would receive incompatible values.
Read the error message carefully to identify which property has the type conflict and what the two types are. The error shows both the derived type's property type and the base type's property type.
// Base class with string property
class Animal {
name: string = "";
}
// Derived class trying to change property type
class Dog extends Animal {
name: number = 0; // ERROR: Property 'name' in type 'Dog' is not assignable to the same property in base type 'Animal'
}The error tells us: Property 'name' in type 'Dog' (type: number) is not assignable to the same property in base type 'Animal' (type: string).
The derived property type must be assignable to (compatible with) the base property type. This usually means the derived type should be the same or a subtype of the base type.
// Base class with Animal type property
class Zoo {
animal: Animal = new Animal();
}
// Derived class - OK: Dog is a subtype of Animal
class DogZoo extends Zoo {
animal: Dog = new Dog(); // OK: Dog extends Animal
}
// Derived class - ERROR: Animal is not a subtype of Dog
class AnimalZoo extends Zoo {
animal: Animal = new Animal(); // Actually OK here, but shows the principle
}For primitive types, they must be exactly the same or compatible through union types.
If you need different behavior, consider using a different property name or a union type that includes the base type.
// Base class
class Vehicle {
speed: number = 0;
}
// WRONG - changing type
class Car extends Vehicle {
speed: string = "fast"; // ERROR
}
// CORRECT - same type
class Car extends Vehicle {
speed: number = 100; // OK
}
// CORRECT - union type that includes the base type
class HybridCar extends Vehicle {
speed: number | "electric" = 0; // OK if "electric" is assignable to number
// Actually, this might still error if "electric" is not assignable to number
}
// BETTER - use a different property
class Car extends Vehicle {
speed: number = 100;
speedDescription: string = "fast"; // Additional property
}If the base property is optional (?), the derived property can be optional or required. If the base property is required, the derived property must also be required (cannot be optional).
interface Base {
name?: string; // Optional
}
// OK - making optional property required
interface Derived extends Base {
name: string; // Required - OK because all Base values have name? (maybe undefined)
}
interface Base2 {
name: string; // Required
}
// ERROR - making required property optional
interface Derived2 extends Base2 {
name?: string; // Optional - ERROR because Base2 requires name
}When using generics with inheritance, ensure type parameters are properly constrained and compatible.
class Container<T> {
value: T;
constructor(value: T) {
this.value = value;
}
}
// ERROR - generic constraint mismatch
class StringContainer extends Container<string> {
value: number; // ERROR: changing type parameter instantiation
}
// CORRECT - same generic instantiation
class StringContainer extends Container<string> {
value: string; // OK - same type
}
// CORRECT - additional methods with same property type
class StringContainer extends Container<string> {
getLength(): number {
return this.value.length;
}
}When multiple interfaces are merged or extended, ensure property types are compatible.
interface A {
prop: string;
}
interface B {
prop: number;
}
// ERROR - conflicting property types
interface C extends A, B {
// prop: string & number is impossible
}
// SOLUTION 1: Use a union type in the extending interface
interface C extends A {
prop: string | number; // Still error: not assignable to A.prop (string)
}
// SOLUTION 2: Omit and redefine
interface C extends Omit<A, "prop">, Omit<B, "prop"> {
prop: string | number; // Now OK - not conflicting with parent interfaces
}
// SOLUTION 3: Use type intersection (not extension)
type C = A & B; // Creates type with prop: string & number (never)If you need different behavior for a property, consider using a getter/setter method instead of overriding the property.
class Base {
private _value: number = 0;
get value(): number {
return this._value;
}
set value(v: number) {
this._value = v;
}
}
class Derived extends Base {
// Override getter with compatible return type
get value(): number {
return super.value * 2; // OK - still returns number
}
}
// ERROR example with property
class Base2 {
value: number = 0;
}
class Derived2 extends Base2 {
value: string = "test"; // ERROR - property type mismatch
}If you frequently encounter type conflicts, consider using composition instead of inheritance. This gives more flexibility with types.
// Instead of inheritance with type conflicts:
class Animal {
name: string = "";
}
// class Dog extends Animal {
// name: DogName; // ERROR if DogName not assignable to string
// }
// Use composition:
class Dog {
private animal: Animal;
name: DogName;
constructor(name: DogName) {
this.animal = new Animal();
this.animal.name = name.toString(); // Convert if needed
this.name = name;
}
getAnimalName(): string {
return this.animal.name;
}
}This error relates to the Liskov Substitution Principle (LSP) in object-oriented design: derived types must be substitutable for their base types without altering program correctness. TypeScript enforces this through structural typing. For properties, covariance is required: the derived property type must be assignable to the base property type. This differs from method parameters which are contravariant when strictFunctionTypes is enabled. The error TS2416 specifically addresses property type compatibility in class inheritance. For interfaces, the same rules apply but TypeScript uses declaration merging which can create complex conflicts. When working with mapped types or conditional types in inheritance, type inference might produce unexpected results. Generic constraints play a crucial role: class Derived<T> extends Base<T> requires the same type parameter, but class Derived<T extends U> extends Base<U> creates compatibility issues. Readonly properties have special rules: a readonly property in base can be made writable in derived (widening), but a writable property in base cannot become readonly in derived (narrowing). Index signatures also participate in type compatibility: [key: string]: T in base requires derived index values to be assignable to T.
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