The Problem
This error pops up when you try to use the new keyword on a class marked with the abstract modifier. Think of an abstract class as a sketch or a blueprint. It defines what a group of related classes should do, but it isn't a finished product itself. Because it's incomplete, TypeScript prevents you from turning it into a live object.
Imagine you're building a logging system. You might run into the error with code like this:
abstract class Logger {
abstract log(message: string): void;
info(message: string) {
console.log(`[INFO]: ${message}`);
}
}
// โ TypeScript Error: Cannot create an instance of an abstract class.
const myLogger = new Logger();
Why This Happens
The abstract keyword serves as a safeguard. Since abstract classes often contain methods without any actual code (abstract methods), creating an instance would be dangerous. If TypeScript allowed this, your application would likely crash the moment you called an unimplemented method at runtime. Even if your class only contains fully written methods, the abstract tag tells the compiler: "This class is for inheritance only."
Practical Solutions
1. Implement a Concrete Subclass
The standard fix involves creating a "concrete" class that extends your abstract base. This child class fills in the blanks by implementing any missing logic. This is the most common approach in 90% of object-oriented TypeScript projects.
abstract class Shape {
abstract getArea(): number;
}
// โ
Fix: Create a specific implementation
class Square extends Shape {
constructor(private width: number) {
super();
}
getArea(): number {
return this.width * this.width;
}
}
// Instantiate the subclass instead of the base
const mySquare = new Square(5);
console.log(mySquare.getArea()); // Output: 25
2. Remove the Abstract Modifier
Ask yourself if the class truly needs to be abstract. If you find yourself wanting to use the base class directly more often than extending it, the abstract keyword is likely unnecessary. Removing it turns the class into a standard, instantiable class.
// Before: abstract class ApiClient { ... }
// After: Just a regular class
class ApiClient {
fetchData(endpoint: string) {
return fetch(`https://api.example.com/${endpoint}`);
}
}
const client = new ApiClient(); // Works perfectly
3. Handling Factory Functions and Generics
You might hit this wall when passing classes into factory functions or Dependency Injection (DI) containers. If a function expects a constructor but you pass an abstract class, the types won't match. This often happens in frameworks like NestJS or when building custom plugins.
abstract class BaseService {}
class AuthService extends BaseService {}
// This helper expects a class that can be instantiated
function createInstance<T>(Ctor: new () => T): T {
return new Ctor();
}
// โ Error: BaseService is abstract
createInstance(BaseService);
// โ
Success: AuthService is concrete
createInstance(AuthService);
How to Verify the Fix
Don't just assume it works. Follow these three steps to be sure:
- Check your IDE: VS Code should immediately remove the red squiggly line under the
newkeyword once you switch to a concrete subclass. - Run the Compiler: Execute
npx tscin your terminal. If the build finishes without the "Cannot create an instance" error, your types are sound. - Test Runtime Logic: Run your code (e.g.,
node dist/index.js) to ensure the subclass methods execute their logic as intended.
Design Tips for the Future
To keep your architecture clean, consider these three patterns:
- Interfaces vs. Abstract Classes: If you only need to define a shape without any shared code, use an
interface. It disappears at runtime, making your bundle smaller. - The "Base" Prefix: Naming your classes
BaseRepositoryorBaseControllerprovides a clear visual cue to other developers that the class isn't meant to be used on its own. - Constructor Access: Even in abstract classes, you can use
protected constructor(). This ensures only subclasses can callsuper(), reinforcing the abstract behavior at the language level.

