TS2416: Property 'X' in type 'Y' is not assignable to the same property in base type 'Z'
ID: typescript/ts2416-property-not-assignable-base
Version Compatibility
| Version | Status | Introduced | Deprecated | Notes |
|---|---|---|---|---|
| 5 | active | — | — | — |
Root Cause
A class overrides a property or method from its base class (or implements an interface member) with an incompatible type. TypeScript enforces that overridden members maintain Liskov Substitution Principle compatibility -- the derived type must be assignable to the base type for that member. Common causes: narrowing a method parameter type in a subclass, changing a return type incompatibly, or overriding a property with a different type.
genericWorkarounds
-
92% success Widen the overriding member's type to be compatible with the base type using union or the base type itself
// Before (error): method parameter is narrower than base class Base { handle(input: string | number): void { /* ... */ } } class Derived extends Base { handle(input: string): void { /* TS2416 */ } } // After: accept the full base type, narrow inside class Derived extends Base { handle(input: string | number): void { if (typeof input === 'string') { // string-specific logic } } }Sources: https://www.typescriptlang.org/docs/handbook/2/classes.html#overriding-methods
-
88% success Use generics in the base class to allow subclasses to specialize types safely
// Before: rigid base type class Base { data: Record<string, unknown> = {}; } class Derived extends Base { data: { name: string; age: number } = { name: '', age: 0 }; // TS2416 } // After: generic base allows type-safe specialization class Base<T extends Record<string, unknown> = Record<string, unknown>> { data: T; constructor(data: T) { this.data = data; } } class Derived extends Base<{ name: string; age: number }> { constructor() { super({ name: '', age: 0 }); } }Sources: https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-classes
-
80% success Use an interface with method overloads instead of class inheritance
// Instead of class inheritance with incompatible overrides, // use an interface with overloaded signatures: interface Handler { handle(input: string): string; handle(input: number): number; handle(input: string | number): string | number; } class MyHandler implements Handler { handle(input: string): string; handle(input: number): number; handle(input: string | number): string | number { if (typeof input === 'string') return input.toUpperCase(); return input * 2; } }Sources: https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads
Dead Ends
Common approaches that don't work:
-
Use 'as any' type assertion on the overriding property to silence the error
80% fail
Casting to any removes type safety at the override point, allowing runtime type mismatches. Code that uses the base class type will pass values the subclass cannot handle, causing runtime errors. This breaks polymorphism and the Liskov Substitution Principle that TypeScript is enforcing.
-
Disable strictFunctionTypes in tsconfig to allow bivariant parameter checking
70% fail
Disabling strictFunctionTypes weakens type checking across the entire project, not just the one override. It allows unsound assignments where a function expecting a broader type is assigned one expecting a narrower type. This masks real bugs elsewhere and does not specifically target TS2416 (which applies to property types, not just function types).