This TypeScript error occurs when you attempt to directly instantiate a class marked as abstract using the 'new' keyword. Abstract classes are designed to be extended by subclasses, not instantiated directly. The fix is to create a concrete subclass that implements all abstract methods and instantiate that instead.
The "Cannot create an instance of an abstract class" error (TS2511) occurs when TypeScript detects an attempt to instantiate an abstract class directly using the new keyword. Abstract classes serve as blueprints or base templates for other classes and cannot be instantiated on their own. Abstract classes in TypeScript can contain both fully implemented methods (concrete methods) and abstract methods that have no implementation. The abstract methods must be implemented by any concrete class that extends the abstract class. This design pattern enforces a contract that derived classes must follow while allowing shared functionality in the base class. When you mark a class as abstract, TypeScript enforces at compile time that: 1. The abstract class cannot be instantiated directly 2. Any class extending it must implement all abstract methods 3. Abstract methods can only exist within abstract classes This error is a safeguard that prevents runtime issues and enforces proper object-oriented design patterns. While TypeScript will throw a compilation error, it still transpiles to JavaScript code (which doesn't have the abstract keyword), so the error is purely a TypeScript feature.
The solution is to create a non-abstract class that extends your abstract class and implements all abstract methods:
// WRONG - Direct instantiation of abstract class
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("Moving...");
}
}
const animal = new Animal(); // ❌ Error TS2511// CORRECT - Create concrete subclass
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("Moving...");
}
}
class Dog extends Animal {
makeSound(): void {
console.log("Woof!");
}
}
const dog = new Dog(); // ✅ Works!
dog.makeSound(); // "Woof!"
dog.move(); // "Moving..."Every abstract method in the parent class must be implemented in the concrete subclass.
Ensure your concrete class implements every abstract method and property:
abstract class ViewModel {
abstract id: string;
abstract getData(): Promise<unknown>;
abstract render(): void;
// Concrete method (has implementation)
log(): void {
console.log(`ViewModel ${this.id}`);
}
}
// WRONG - Missing implementations
class UserViewModel extends ViewModel {
id = "user-vm";
// ❌ Error: Class 'UserViewModel' must implement getData() and render()
}
// CORRECT - All abstract members implemented
class UserViewModel extends ViewModel {
id = "user-vm";
async getData(): Promise<unknown> {
return { name: "John", age: 30 };
}
render(): void {
console.log("Rendering user view...");
}
}
const userVm = new UserViewModel(); // ✅ Works!TypeScript will give you a compilation error if any abstract member is not implemented.
If you need to reference the type in function parameters or variables, use the abstract class as a type but instantiate concrete classes:
abstract class Shape {
abstract area(): number;
abstract perimeter(): number;
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
area(): number {
return Math.PI * this.radius ** 2;
}
perimeter(): number {
return 2 * Math.PI * this.radius;
}
}
class Rectangle extends Shape {
constructor(private width: number, private height: number) {
super();
}
area(): number {
return this.width * this.height;
}
perimeter(): number {
return 2 * (this.width + this.height);
}
}
// Use abstract class as TYPE
function calculateArea(shape: Shape): number {
return shape.area();
}
// But instantiate concrete classes
const circle = new Circle(5);
const rectangle = new Rectangle(4, 6);
console.log(calculateArea(circle)); // ✅ Works
console.log(calculateArea(rectangle)); // ✅ WorksThe abstract class serves as a contract/interface that multiple concrete implementations follow.
If you actually need to instantiate the class directly, remove the abstract keyword and provide implementations for all methods:
// Before: abstract class
abstract class BaseController {
abstract handle(): void;
}
// const controller = new BaseController(); // ❌ Error
// After: concrete class
class BaseController {
handle(): void {
console.log("Default handler");
}
}
const controller = new BaseController(); // ✅ Works!However, consider if removing abstract is the right design choice. Abstract classes are intentionally designed to be extended, not instantiated. If you need instantiation, you might want:
- A regular class instead
- An interface with a factory function
- A concrete class with optional overrides
When using abstract classes with DI containers, ensure you register concrete implementations:
abstract class DatabaseService {
abstract connect(): Promise<void>;
abstract query(sql: string): Promise<unknown>;
}
class PostgresService extends DatabaseService {
async connect(): Promise<void> {
console.log("Connecting to PostgreSQL...");
}
async query(sql: string): Promise<unknown> {
console.log(`Executing: ${sql}`);
return [];
}
}
// WRONG - Trying to inject abstract class
// const db = container.resolve(DatabaseService); // ❌ Can't instantiate
// CORRECT - Register and inject concrete class
class MyApp {
constructor(private db: DatabaseService) {}
async start() {
await this.db.connect();
}
}
// In DI container setup:
// container.register(DatabaseService, { useClass: PostgresService });
// OR manually:
const app = new MyApp(new PostgresService()); // ✅ WorksThe abstract class provides the type contract, but you inject a concrete implementation.
### Abstract Classes vs Interfaces
TypeScript offers both abstract classes and interfaces for defining contracts. Understanding when to use each:
Use abstract classes when:
- You need to share implementation (concrete methods) across subclasses
- You want to provide default behavior that subclasses can optionally override
- You need constructor logic that runs for all subclasses
- You're modeling an "is-a" relationship
Use interfaces when:
- You only need to define a contract without implementation
- You need multiple inheritance (a class can implement multiple interfaces but extend only one abstract class)
- You want pure type-checking with no runtime code
Example comparison:
// Abstract class: Can have implementation
abstract class BaseRepository<T> {
protected items: T[] = [];
abstract find(id: string): T | undefined;
// Shared implementation
getAll(): T[] {
return [...this.items];
}
}
// Interface: No implementation
interface Repository<T> {
find(id: string): T | undefined;
getAll(): T[];
}
// Concrete class must implement everything with interface
class UserRepository implements Repository<User> {
private items: User[] = [];
find(id: string): User | undefined {
return this.items.find(u => u.id === id);
}
getAll(): User[] {
return [...this.items]; // Must reimplement
}
}### Abstract Constructors
Abstract classes can have constructors, and they're called when subclasses are instantiated:
abstract class Component {
protected readonly id: string;
constructor(id: string) {
this.id = id;
console.log(`Component ${id} initialized`);
}
abstract render(): void;
}
class Button extends Component {
constructor(id: string, private label: string) {
super(id); // Must call parent constructor
}
render(): void {
console.log(`<button>${this.label}</button>`);
}
}
const btn = new Button("btn-1", "Click me");
// Logs: "Component btn-1 initialized"Subclasses must call super() before accessing this if they override the constructor.
### Protected vs Private in Abstract Classes
Abstract classes often use protected for members that subclasses should access:
abstract class DataSource {
protected connection: Connection;
private secret: string = "api-key";
abstract fetch(): Promise<unknown>;
protected log(message: string): void {
console.log(`[${new Date().toISOString()}] ${message}`);
}
}
class ApiDataSource extends DataSource {
async fetch(): Promise<unknown> {
this.log("Fetching from API"); // ✅ Can access protected
// this.secret; // ❌ Cannot access private
return {};
}
}Rules:
- protected: Accessible in the class and all subclasses
- private: Only accessible within the defining class
- public: Accessible everywhere (default)
### Abstract Static Members (Not Supported)
TypeScript does not support abstract static members:
abstract class Model {
abstract static tableName: string; // ❌ Error: Static members cannot be abstract
}Workaround using interfaces and type assertions:
interface ModelConstructor {
tableName: string;
}
abstract class Model {
static tableName: string;
abstract save(): Promise<void>;
}
class User extends Model {
static tableName = "users";
async save(): Promise<void> {
const tableName = (this.constructor as typeof Model).tableName;
console.log(`Saving to ${tableName}`);
}
}### Checking for Abstract Classes at Runtime
TypeScript's abstract keyword is erased during compilation. At runtime, there's no native way to check if a class is abstract. However, you can use conventions:
abstract class Base {
constructor() {
if (new.target === Base) {
throw new Error("Cannot instantiate abstract class");
}
}
}
class Derived extends Base {
// OK
}
const d = new Derived(); // ✅ Works
const b = new Base(); // ✅ Compiles but throws at runtimeThis pattern uses new.target to detect direct instantiation and throw a runtime error, adding an extra layer of protection beyond TypeScript's compile-time check.
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