How to fix Cannot assign an abstract constructor type to a non-abstract constructor type in TypeScript
This error occurs when attempting to assign or pass an abstract class to a function parameter that expects a concrete constructor. TypeScript prevents this because abstract classes cannot be instantiated directly. The solution involves using the abstract constructor type syntax (available in TypeScript 4.2+) to explicitly allow abstract constructors in function signatures.
Quick fix
Fixes Cannot assign an abstract constructor type to a non-abstract constructor type
// Non-abstract - expects concrete class only
function createInstance(Ctor: new () => Animal) {
return new Ctor(); // This calls the constructor directly
}
// ❌ This fails with error
abstract class AnimalBase {
abstract sound: string;
}
const result = createInstance(AnimalBase); // ERROR: Cannot assign abstract constructor- Understand the difference between abstract and non-abstract constructor signatures
- Add abstract modifier to constructor signature in function parameter (TypeScript 4.2+)
- Define a reusable abstract constructor type
- Use abstract constructor types with dependency injection
// Non-abstract - expects concrete class onlyfunction createInstance(Ctor: new () => Animal) {return new Ctor(); // This calls the constructor directly}// ❌ This fails with errorabstract class AnimalBase {abstract sound: string;}const result = createInstance(AnimalBase); // ERROR: Cannot assign abstract constructorCannot assign an abstract constructor type to a non-abstract constructor type
What this error means
This error reflects TypeScript's strict type system for constructor signatures. When a function defines a parameter expecting a constructor type like `new () => SomeClass`, TypeScript assumes only concrete classes can be passed because the constructor is meant to be called directly to create instances. Abstract classes cannot be instantiated, so TypeScript prevents passing them to such signatures. The error commonly arises in scenarios involving dependency injection, factory functions, class decorators, or utility functions that work with constructors. TypeScript is ensuring type safety by preventing code that would fail at runtime—you cannot call `new` on an abstract class, even though you can pass abstract classes to other parts of your code. TypeScript 4.2 introduced support for abstract constructor signatures (using the `abstract new` syntax) to solve this problem, allowing developers to explicitly signal that a parameter accepts both abstract and concrete constructors.
Typical symptoms
- TypeScript compiler error when passing an abstract class to a function expecting a constructor
- Error appears when using abstract classes with dependency injection decorators like @injectable()
- Red squiggly lines in VS Code when trying to assign an abstract class to a variable typed as a constructor
- Build fails with "Cannot assign an abstract constructor type" during TypeScript compilation
- Error when using ConstructorParameters<typeof AbstractClass>
- Class decorators fail to apply to abstract classes with error about constructor type mismatch
- Factory functions or utilities that accept class constructors reject abstract classes
Common causes
- Function signature declares constructor type without abstract modifier (e.g., `Ctor: new () => Base`)
- Passing an abstract class to a function that expects a concrete constructor type
- Using utility types like ConstructorParameters or InstanceType with abstract classes
- Applying decorators to abstract classes when decorator expects non-abstract constructor
- TypeScript version is older than 4.2 (abstract constructor types not supported)
- Type definition from external library uses non-abstract constructor signature
- Mixin pattern or higher-order functions expecting only concrete constructors
How to fix "Cannot assign an abstract constructor type to a non-abstract constructor type"
A non-abstract constructor signature expects a concrete class that can be instantiated:
// Non-abstract - expects concrete class only
function createInstance(Ctor: new () => Animal) {
return new Ctor(); // This calls the constructor directly
}
// ❌ This fails with error
abstract class AnimalBase {
abstract sound: string;
}
const result = createInstance(AnimalBase); // ERROR: Cannot assign abstract constructorAn abstract constructor signature allows both abstract and concrete classes:
// Abstract - accepts both abstract and concrete
function checkType(Ctor: abstract new () => Animal) {
console.log(Ctor.name); // Just checks the constructor, doesn't call it
}
// ✅ This works with both
checkType(AnimalBase); // Abstract class - OK
checkType(Dog); // Concrete class - OKIf your function doesn't actually need to instantiate the class (just reference it), use the abstract new syntax:
// Before (fails with abstract classes)
function processClass(Ctor: new () => Base) {
return Ctor.name;
}
// After (accepts both abstract and concrete)
function processClass(Ctor: abstract new () => Base) {
return Ctor.name; // Now works with abstract classes
}
abstract class AnimalBase {
abstract makeSound(): void;
}
class Dog extends AnimalBase {
makeSound() { console.log("Woof"); }
}
processClass(AnimalBase); // ✅ Works
processClass(Dog); // ✅ WorksFor cleaner code, define a type alias for abstract constructors and use it throughout your codebase:
// Define once, use everywhere
type AbstractConstructor<T = {}> = abstract new (...args: any[]) => T;
type Constructor<T = {}> = new (...args: any[]) => T;
// Function that needs abstract classes
function mixinClass<T extends Constructor>(Base: T) {
return class extends Base {
constructor(...args: any[]) {
super(...args);
console.log("Mixin applied");
}
};
}
// Function that just inspects classes
function getClassName(Ctor: AbstractConstructor) {
return Ctor.name;
}
getClassName(SomeAbstractClass); // ✅ Works
getClassName(SomeConcreteClass); // ✅ WorksWhen working with DI decorators or frameworks, define constructor types to accept abstract classes:
// Type definition for service constructor
type ServiceConstructor = abstract new (...args: any[]) => any;
// Decorator that works with both abstract and concrete services
function Service(config: any) {
return function <T extends ServiceConstructor>(constructor: T) {
// Register service, metadata, etc.
console.log(`Registered ${constructor.name}`);
return constructor;
};
}
// Now this works with abstract classes
@Service({ scope: "transient" })
abstract class BaseUserService {
abstract getUser(id: string): Promise<User>;
}
// And concrete implementations
@Service({ scope: "transient" })
class UserService extends BaseUserService {
getUser(id: string) {
return Promise.resolve({ id, name: "John" });
}
}If you're using a library whose type definitions use non-abstract constructor signatures, you have options:
Option 1: Use a type assertion (temporary workaround)
function registerClass(Ctor: new () => Base) {
// ...
}
// Bypass the type checker
registerClass(AbstractClass as any);Option 2: Create a wrapper function
type AbstractConstructor<T> = abstract new (...args: any[]) => T;
function registerAbstractClass<T>(Ctor: AbstractConstructor<T>) {
// Convert to expected type
return registerClass(Ctor as any);
}
registerAbstractClass(MyAbstractClass); // ✅ Type-safe wrapperOption 3: Report issue to library maintainers
File an issue or pull request to update the library's type definitions to use abstract constructor types.
When creating functions that wrap or enhance classes, use abstract constructor types:
type AbstractConstructor<T = {}> = abstract new (...args: any[]) => T;
// Higher-order function that wraps a class
function withLogging<T extends AbstractConstructor>(
Base: T
): T {
return class extends Base as any {
constructor(...args: any[]) {
super(...args);
console.log(`${Base.name} instantiated`);
}
} as T;
}
// Abstract class to wrap
abstract class Repository {
abstract find(id: string): any;
}
// Concrete implementation
class UserRepository extends Repository {
find(id: string) {
return { id, name: "User" };
}
}
// Apply wrapper to both abstract and concrete
const WrappedAbstractRepo = withLogging(Repository); // ✅ Works
const WrappedConcreteRepo = withLogging(UserRepository); // ✅ WorksThe abstract constructor type syntax requires TypeScript 4.2 or later. Check your current version:
tsc --versionIf you're on an older version, upgrade TypeScript:
# npm
npm install --save-dev typescript@latest
# yarn
yarn add -D typescript@latest
# pnpm
pnpm add -D typescript@latestVerify the upgrade by checking the version again and recompiling your project. After upgrading, you can use the abstract new syntax in your code.
Advanced notes
The abstract constructor type is a subtle but important distinction in TypeScript's type system. When you declare a function parameter as new () => T, the compiler infers that the function will call new on the constructor. Since abstract classes cannot be instantiated (they cannot be called with new), TypeScript rejects abstract classes. Using abstract new () => T signals that the function won't call the constructor directly—it just references the constructor type.
This pattern is especially useful in the mixin pattern, where a function takes a class constructor, extends it, and returns the extended class. The original constructor is never called with new, just referenced and extended. Similarly, in dependency injection frameworks, containers often just register constructors without immediately instantiating them.
The distinction between new () => T and abstract new () => T is purely a type-checking construct—it doesn't exist in the compiled JavaScript. This is a design pattern borrowed from other languages' type systems to provide more precise control over what can be passed to functions.
For complex inheritance hierarchies, ensure you understand that a function accepting abstract new () => Base can receive any class that extends Base, whether Base itself is abstract or concrete. This provides maximum flexibility while maintaining type safety.
When working with libraries that have strict constructor types, consider whether type assertions or wrapper functions are appropriate trade-offs. If a library is widely used, contributing a fix upstream often benefits the entire community.
Frequently asked questions
- What does "Cannot assign an abstract constructor type to a non-abstract construct" mean?
- This error reflects TypeScript's strict type system for constructor signatures. When a function defines a parameter expecting a constructor type like new () => SomeClass, TypeScript assumes only concrete classes can be passed because the constructor is meant to be called directly to create instances. Abstract classes cannot be instantiated, so TypeScript prevents passing them to such signatures.
- How do I fix "Cannot assign an abstract constructor type to a non-abstract construct"?
- This error occurs when attempting to assign or pass an abstract class to a function parameter that expects a concrete constructor. TypeScript prevents this because abstract classes cannot be instantiated directly. The solution involves using the abstract constructor type syntax (available in TypeScript 4.2+) to explicitly allow abstract constructors in function signatures.
- What causes "Cannot assign an abstract constructor type to a non-abstract construct"?
- Function signature declares constructor type without abstract modifier (e.g., Ctor: new () => Base) Passing an abstract class to a function that expects a concrete constructor type Using utility types like ConstructorParameters or InstanceType with abstract classes
- Is the "Cannot assign an abstract constructor type to a non-abstract construct" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚TypeScript Official Documentation
- 📖TypeScript 4.2 Release Notes - Abstract Constructor Types(Official Docs)
- 📖TypeScript Handbook - Abstract Classes and Members(Official Docs)
- 🐙
- 🐙