TypeScript’s compiler is throwing a fit because a property you’re trying to assign to an object doesn’t match the type definition of that object’s base class. This means you’re either trying to put the wrong kind of data in a spot, or you’ve fundamentally misunderstood how inheritance is working here.

Here’s the breakdown of why this happens and how to fix it, starting with the most common culprits:

1. Incorrect Property Type in Derived Class

  • Diagnosis: You’ve declared a property in your derived class with a type that’s incompatible with the same-named property in the base class.
  • Common Cause: Accidentally narrowing the type. For example, the base class has name: string, and your derived class tries name: "Alice".
  • Fix: Ensure the derived class property’s type is either identical to or a supertype of the base class property’s type.
    class Base {
        value: string | number;
        constructor(value: string | number) {
            this.value = value;
        }
    }
    
    class Derived extends Base {
        // Incorrect: Narrowing the type too much
        // value: string = "hello";
    
        // Correct: Keeping it compatible
        value: string | number = 0;
    
        constructor(value: string | number) {
            super(value);
        }
    }
    
  • Why it works: TypeScript enforces that derived types must be assignable to their base types. If you narrow a property’s type in the derived class, it might become impossible to assign an instance of the derived class to a variable typed as the base class, breaking this rule.

2. Missing override Keyword (TypeScript 4.3+)

  • Diagnosis: You’ve declared a property in the derived class with the same name as a property in the base class, but you haven’t explicitly told TypeScript you intend to override it.
  • Common Cause: Forgetting to add override when adding a new property that happens to share a name with a base class property, or when the base class property is protected or private (though TS2416 usually implies it’s accessible).
  • Fix: Add the override keyword before the property declaration in the derived class.
    class Base {
        protected message: string = "Base message";
    }
    
    class Derived extends Base {
        // Incorrect without 'override' if 'message' was public or accessible
        // message: string = "Derived message";
    
        // Correct
        override message: string = "Derived message";
    
        constructor() {
            super();
        }
    }
    
  • Why it works: The override keyword explicitly signals to the TypeScript compiler that you intend to replace a member from the base class. This allows the compiler to check for type compatibility and ensures you’re not accidentally creating a new, unrelated property.

3. Incorrect Constructor Parameter Assignment

  • Diagnosis: The constructor of your derived class doesn’t correctly pass arguments to the base class constructor, or it attempts to assign a value to a base class property before super() is called.
  • Common Cause: Trying to initialize a base class property directly in the derived class’s constructor body before calling super().
  • Fix: Ensure the super() call is the first statement in the derived constructor and that it passes the correct arguments. Any properties that are part of the base class should be initialized via the super() call or by the base class itself.
    class Base {
        name: string;
        constructor(name: string) {
            this.name = name;
        }
    }
    
    class Derived extends Base {
        // Incorrect: Trying to assign to 'name' before super()
        // constructor(name: string) {
        //     this.name = name; // Error: Property 'name' is not assignable to 'Base' before super() call.
        //     super(name);
        // }
    
        // Correct
        constructor(name: string) {
            super(name); // Pass the argument to the base constructor
        }
    }
    
  • Why it works: In JavaScript and TypeScript, derived class constructors must call super() before they can use this. This ensures the base class instance is properly initialized. Assigning to base class properties before super() is called is disallowed.

4. Mismatched Access Modifiers

  • Diagnosis: You’re trying to override a property in the derived class with a more restrictive access modifier than the base class.
  • Common Cause: Base class has public value: string, derived class has private value: string.
  • Fix: The derived class’s property must have the same or a less restrictive access modifier than the base class property.
    class Base {
        public data: number = 10;
    }
    
    class Derived extends Base {
        // Incorrect: Cannot make a public property private
        // private data: number = 20;
    
        // Correct: Can keep it public, or make it protected if base was protected
        override data: number = 20;
    
        constructor() {
            super();
        }
    }
    
  • Why it works: This rule maintains the contract of the base class. If external code can access baseInstance.data, it must also be able to access derivedInstance.data if derivedInstance is treated as a Base instance. Making it private would break this.

5. Readonly Mismatch

  • Diagnosis: The base class has a readonly property, but the derived class tries to assign a value to it in a way that violates the readonly constraint.
  • Common Cause: Trying to assign to a readonly property in the derived class’s constructor body after super() has been called, or attempting to assign to it outside the constructor.
  • Fix: If a property is readonly in the base class, it must also be readonly in the derived class, and it can only be assigned a value within the constructor of the derived class (or the base class, if applicable).
    class Base {
        readonly id: string;
        constructor(id: string) {
            this.id = id;
        }
    }
    
    class Derived extends Base {
        // Incorrect: Trying to reassign after constructor or outside constructor
        // constructor(id: string) {
        //     super(id);
        //     this.id = "new-id"; // Error: Cannot assign to 'id' because it is a read-only property.
        // }
    
        // Correct: Assigning in the derived constructor is allowed
        constructor(id: string) {
            super(id);
            // If you need to *derive* the ID, do it here.
            // This.id is already initialized by super(id)
        }
    
        // If you want a *different* readonly property, declare it separately
        override readonly uniqueCode: string = "XYZ"; // This is a new readonly property
    }
    
  • Why it works: The readonly keyword signifies that a property’s value should not change after initialization. By requiring it in the derived class and restricting assignments, TypeScript ensures this immutability is maintained across the inheritance chain.

The next error you’ll likely encounter after resolving TS2416 is a Type mismatch in method arguments or return types if you’ve also inadvertently altered method signatures during your refactoring.

Want structured learning?

Take the full Typescript course →