This error occurs when a class implements an interface but one or more of its properties or methods have incompatible types or signatures. The fix involves aligning the types, signatures, and access modifiers between the class and interface.
When you declare that a class implements an interface in TypeScript, you're creating a contract. The `implements` clause checks that your class satisfies the interface's shape—but it goes further than just checking that properties exist. The "Class incorrectly implements interface" error means that while your class might have the right property or method names, their types, signatures, or accessibility don't match what the interface requires. This could be a return type mismatch, parameter differences, incompatible property types, or access modifier conflicts. TypeScript's type checker uses structural typing to verify that the class implementation is compatible with the interface contract. This error is your early warning system that prevents runtime type mismatches and helps maintain type safety across your codebase.
TypeScript provides specific error details about what's wrong. For example:
Class 'UserService' incorrectly implements interface 'IUserService'.
Types of property 'getUser' are incompatible.
Type '() => User | null' is not assignable to type '() => User'.
Type 'null' is not assignable to type 'User'.This tells you:
1. The class is UserService
2. The problematic property is getUser
3. The class returns User | null but the interface expects User
4. The specific issue is that null isn't acceptable
Focus on the property name and type mismatch details.
Open both files side by side and check the problematic property:
// Interface definition
interface IUserService {
getUser(): User; // Returns User (not nullable)
email: string; // string type
age: number; // number type
}
// Class implementation
class UserService implements IUserService {
email: string = "";
age: string = "25"; // ❌ WRONG: string should be number
getUser(): User | null { // ❌ WRONG: should return User, not User | null
return null;
}
}Look for:
- Different types (string vs number, User vs User | null)
- Different signatures (parameters, return types)
- Missing readonly/optional modifiers
Ensure all property types match exactly:
// ❌ WRONG - type mismatch
interface IConfig {
port: number;
enabled: boolean;
}
class Config implements IConfig {
port: string = "3000"; // ERROR: string is not number
enabled: number = 1; // ERROR: number is not boolean
}
// ✅ CORRECT - types match
class Config implements IConfig {
port: number = 3000;
enabled: boolean = true;
}If you need nullable types, update the interface:
interface IUserService {
getUser(): User | null; // Now allows null
}
class UserService implements IUserService {
getUser(): User | null { // ✅ Now matches
return null;
}
}Method parameters and return types must match precisely:
// ❌ WRONG - return type mismatch
interface ILogger {
log(message: string): void;
}
class Logger implements ILogger {
log(message: string): boolean { // ERROR: returns boolean, not void
console.log(message);
return true;
}
}
// ✅ CORRECT - void return
class Logger implements ILogger {
log(message: string): void {
console.log(message);
}
}Parameter mismatches also cause errors:
// ❌ WRONG - parameter type differs
interface ICalculator {
add(a: number, b: number): number;
}
class Calculator implements ICalculator {
add(a: string, b: string): number { // ERROR: params should be number
return parseInt(a) + parseInt(b);
}
}
// ✅ CORRECT
class Calculator implements ICalculator {
add(a: number, b: number): number {
return a + b;
}
}Interface members are public by default. Class members can't be more restrictive:
// ❌ WRONG - private member on public interface property
interface IService {
getData(): string; // implicitly public
}
class Service implements IService {
private getData(): string { // ERROR: can't be private
return "data";
}
}
// ✅ CORRECT - make it public
class Service implements IService {
public getData(): string { // or just: getData(): string
return "data";
}
}Protected members also cause this error:
// ❌ WRONG
class Service implements IService {
protected getData(): string { // ERROR: can't be protected
return "data";
}
}The readonly modifier must match between interface and class:
// ❌ WRONG - readonly mismatch
interface IConfig {
readonly apiKey: string;
}
class Config implements IConfig {
apiKey: string = "key123"; // ERROR: missing readonly
}
// ✅ CORRECT
class Config implements IConfig {
readonly apiKey: string = "key123";
}Alternatively, if the interface doesn't need readonly, remove it:
interface IConfig {
apiKey: string; // not readonly
}
class Config implements IConfig {
apiKey: string = "key123"; // ✅ matches
}Optional properties (?) in interfaces should match the class:
// ❌ WRONG - optional mismatch
interface IUser {
name: string;
email?: string; // optional
}
class User implements IUser {
name: string = "";
email: string = ""; // Not optional in class - this is actually OK
}
// This is fine because the class can be more specific than the interfaceHowever, you can't make required properties optional:
// ❌ WRONG - required property made optional
interface IUser {
name: string; // required
}
class User implements IUser {
name?: string; // ERROR: can't make it optional
}
// ✅ CORRECT
class User implements IUser {
name: string = "";
}Generic constraints must be compatible:
// ❌ WRONG - incompatible generic types
interface IRepository<T> {
save(item: T): void;
}
class UserRepository implements IRepository<User> {
save(item: string): void { // ERROR: expects User, got string
// ...
}
}
// ✅ CORRECT
class UserRepository implements IRepository<User> {
save(item: User): void {
// ...
}
}Or with bounded generics:
interface IProcessor<T extends object> {
process(data: T): void;
}
// ❌ WRONG - string doesn't extend object
class StringProcessor implements IProcessor<string> {
process(data: string): void { }
}
// ✅ CORRECT - User extends object
class UserProcessor implements IProcessor<User> {
process(data: User): void { }
}### Private/Protected Members in Extended Interfaces
When an interface extends a class with private or protected members, only that class or its subclasses can implement the interface:
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
// ❌ ERROR - SelectableControl requires private 'state' from Control
class Image implements SelectableControl {
select() { }
}
// ✅ CORRECT - extends Control, inherits private state
class Button extends Control implements SelectableControl {
select() { }
}This pattern is used to restrict who can implement an interface.
### Bivariance and Method Signatures
TypeScript allows method parameter bivariance for flexibility, but this can sometimes cause unexpected compatibility:
interface IHandler {
handle(event: MouseEvent): void;
}
class Handler implements IHandler {
// This is technically allowed (contravariance)
handle(event: Event): void {
console.log(event);
}
}For stricter checking, enable strictFunctionTypes in tsconfig.json.
### Index Signatures
Classes implementing interfaces with index signatures must include them:
interface StringMap {
[key: string]: string;
}
// ❌ ERROR - missing index signature
class Config implements StringMap {
apiKey: string = "key";
}
// ✅ CORRECT
class Config implements StringMap {
[key: string]: string;
apiKey: string = "key";
}### Constructor Signatures
Interfaces can't check constructor signatures via implements. Constructors are on the static side of the class:
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick(): void;
}
// ❌ ERROR - constructor isn't checked by implements
class Clock implements ClockConstructor {
constructor(h: number, m: number) { }
tick() { }
}
// ✅ Use a factory function instead
function createClock(
ctor: ClockConstructor,
hour: number,
minute: number
): ClockInterface {
return new ctor(hour, minute);
}### Type Assertions vs Implements
You can use type assertions to bypass compile-time checks, but this removes safety:
interface IUser {
name: string;
email: string;
}
// ❌ Unsafe - no type checking
const user = {
name: "John"
// email is missing!
} as IUser;
// ✅ Better - use implements for compile-time safety
class User implements IUser {
name: string = "";
email: string = ""; // Compiler forces you to add this
}Always prefer implements for classes to get full type-checking benefits.
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