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.
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.
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.
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.
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