This TypeScript compiler error occurs when you try to declare abstract methods in a regular (non-abstract) class. The fix is to add the 'abstract' keyword to the class declaration, making it an abstract base class that cannot be instantiated directly.
TypeScript's abstract classes and methods are designed to create base classes that define a contract for derived classes. An abstract method is a method signature without implementation—it declares what the method should look like but leaves the actual implementation to child classes. The error TS1244 appears when you attempt to use the `abstract` keyword on a method inside a regular class. TypeScript enforces a strict rule: abstract methods can only exist within abstract classes. This ensures that classes with incomplete implementations (abstract methods) cannot be instantiated directly. An abstract class serves as a blueprint or template. It can contain both concrete methods (with implementations) and abstract methods (without implementations). Child classes that extend an abstract class must provide implementations for all abstract methods, or they themselves must be declared abstract.
The most direct fix is to mark the class containing abstract methods as abstract:
// BEFORE: Error TS1244
class Animal {
abstract makeSound(): void; // Error!
}
// AFTER: Add 'abstract' to the class
abstract class Animal {
abstract makeSound(): void; // Now valid
}This tells TypeScript that this class is intended as a base class and cannot be instantiated directly:
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
// Abstract method - no implementation
abstract makeSound(): void;
// Concrete method - has implementation
move(distance: number): void {
console.log(`${this.name} moved ${distance} meters`);
}
}
// Cannot instantiate abstract class
// const animal = new Animal("Generic"); // Error!
// Must create a concrete subclass
class Dog extends Animal {
makeSound(): void {
console.log("Woof! Woof!");
}
}
const dog = new Dog("Buddy");
dog.makeSound(); // "Woof! Woof!"
dog.move(10); // "Buddy moved 10 meters"If you don't actually need an abstract method, provide a concrete implementation instead:
// BEFORE: Abstract method in regular class
class Shape {
abstract calculateArea(): number; // Error!
}
// OPTION 1: Make it concrete with default implementation
class Shape {
calculateArea(): number {
return 0; // Default implementation
}
}
// OPTION 2: Make it concrete with actual logic
class Shape {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
calculateArea(): number {
return this.width * this.height;
}
}Choose this approach when all instances of the class should have the same behavior, or when you can provide a sensible default implementation.
If you're only defining method signatures without any shared implementation, consider using an interface instead:
// BEFORE: Abstract class with only abstract methods
abstract class Vehicle {
abstract start(): void;
abstract stop(): void;
}
// AFTER: Use interface for pure contracts
interface Vehicle {
start(): void;
stop(): void;
}
// Implement the interface
class Car implements Vehicle {
start(): void {
console.log("Car started");
}
stop(): void {
console.log("Car stopped");
}
}
// Can implement multiple interfaces
interface Driveable {
drive(distance: number): void;
}
class Car implements Vehicle, Driveable {
start(): void {
console.log("Car started");
}
stop(): void {
console.log("Car stopped");
}
drive(distance: number): void {
console.log(`Driving ${distance}km`);
}
}Interfaces are lighter-weight and more flexible when you don't need shared implementation code.
When you create an abstract class, any child class must implement all abstract methods unless the child is also abstract:
abstract class Database {
abstract connect(): Promise<void>;
abstract disconnect(): Promise<void>;
abstract query(sql: string): Promise<any>;
// Concrete method shared by all databases
log(message: string): void {
console.log(`[DB] ${message}`);
}
}
// Must implement ALL abstract methods
class PostgresDB extends Database {
connect(): Promise<void> {
this.log("Connecting to PostgreSQL...");
return Promise.resolve();
}
disconnect(): Promise<void> {
this.log("Disconnecting from PostgreSQL...");
return Promise.resolve();
}
query(sql: string): Promise<any> {
this.log(`Executing: ${sql}`);
return Promise.resolve([]);
}
}
// Valid instance
const db = new PostgresDB();
db.connect();If you don't implement all abstract methods, TypeScript will show another error (TS2515).
### When to Use Abstract Classes vs Interfaces
Use abstract classes when:
- You need to share implementation code between related classes
- You have common fields or properties
- You want to provide default behavior that can be overridden
- You're building a class hierarchy with inheritance
Use interfaces when:
- You only need to define a contract (method signatures)
- You want a class to implement multiple contracts
- You're working with pure data structures
- You don't need shared implementation
### Abstract Properties
You can also declare abstract properties (not just methods):
abstract class Component {
abstract name: string; // Must be provided by child class
abstract render(): string;
// Concrete property with default
isVisible: boolean = true;
}
class Button extends Component {
name = "Button"; // Must provide
render(): string {
return `<button>${this.name}</button>`;
}
}### Protected Members in Abstract Classes
Abstract classes often use protected members to share data with child classes:
abstract class HttpClient {
protected baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
abstract get<T>(path: string): Promise<T>;
abstract post<T>(path: string, data: any): Promise<T>;
// Protected helper available to child classes
protected buildUrl(path: string): string {
return `${this.baseUrl}${path}`;
}
}
class FetchClient extends HttpClient {
async get<T>(path: string): Promise<T> {
const url = this.buildUrl(path); // Can use protected method
const response = await fetch(url);
return response.json();
}
async post<T>(path: string, data: any): Promise<T> {
const url = this.buildUrl(path);
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(data)
});
return response.json();
}
}### Multiple Inheritance Workaround
TypeScript doesn't support multiple inheritance, but you can combine abstract classes with interfaces:
// Base functionality via abstract class
abstract class Logger {
abstract log(message: string): void;
}
// Additional contracts via interfaces
interface Serializable {
serialize(): string;
}
interface Validatable {
validate(): boolean;
}
// Combine them
class UserModel extends Logger implements Serializable, Validatable {
log(message: string): void {
console.log(`[UserModel] ${message}`);
}
serialize(): string {
return JSON.stringify(this);
}
validate(): boolean {
return true;
}
}### Abstract Classes in Design Patterns
Abstract classes are commonly used in design patterns like:
- Template Method Pattern: Define algorithm skeleton in abstract class, let subclasses override specific steps
- Factory Pattern: Abstract factory defines creation methods, concrete factories implement them
- Strategy Pattern: Abstract strategy class with multiple concrete implementations
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