Quick Overview
An `abstract` class cannot be instantiated, and its abstract methods have no body because every subclass must supply them. That is how a base class fixes an algorithm's shape while leaving each step open.
An Abstract Class is a base class that CANNOT be instantiated directly using new. It exists solely to be extended by child subclasses.
Unlike interfaces (which are completely erased at compile time), abstract classes exist at runtime while allowing you to share concrete implementation methods alongside abstract method contracts.
Mark the class and missing method signatures with the abstract keyword:
abstract class BaseRepository<T> {
// Shared concrete helper method
public logOperation(op: string): void {
console.log(`[DB OP]: ${op} at ${new Date().toISOString()}`);
}
// Abstract method contract (Subclasses MUST implement this!)
abstract findById(id: string): Promise<T | null>;
abstract save(entity: T): Promise<void>;
}
interface UserEntity { id: string; name: string }
class UserRepository extends BaseRepository<UserEntity> {
async findById(id: string): Promise<UserEntity | null> {
this.logOperation(`findById: ${id}`);
return { id, name: "Alex" };
}
async save(entity: UserEntity): Promise<void> {
this.logOperation(`save: ${entity.id}`);
}
}
| Feature | Abstract Class | Interface |
|---|---|---|
| Runtime JS Code | Generates real JS class code | Erased completely at compile time |
| Concrete Methods | Can contain real code methods | Signatures only (No code) |
| Direct Instantiation | Impossible (new Base() errors) | Impossible |
new BaseClass(): Trying to instantiate an abstract class directly throws compile error: Cannot create an instance of an abstract class.Use abstract classes for template design patterns. Next, let's explore Ambient Declarations and Declaration Files (.d.ts)!
TypeScript erases entirely at build time, so a `string` annotation produces no runtime check whatsoever. A value arriving from an API is whatever the server actually sent, regardless of what the annotation claims.
`let x = "GET"` infers `string` because the binding can change; `const x = "GET"` infers the literal `"GET"`. That widening rule is why object properties need `as const` to stay narrow.
`string[]` is any number of strings; `[string, number]` is exactly two, in that order. `readonly` removes the mutating methods at compile time, so `push` becomes a type error rather than a runtime surprise.