Mastering Classes and Interfaces in TypeScript: From Fundamentals to Advanced Patterns
Core Concepts
TypeScript leverages classes and interfaces to enforce type safety while supporting object-oriented design. Classes provide concrete implementations, whereas interfaces define structural contracts without implementation.
class Employee {
name: string;
department: string;
constructor(name: string, dept: string) {
this.name = name;
this.department = dept;
}
introduce() {
console.log(`Hi, I'm ${this.name} from ${this.department}.`);
}
}
const emp = new Employee("Sarah", "Engineering");
emp.introduce(); // Hi, I'm Sarah from Engineering.
Interfaces specify the shape an object must conform to:
interface Drawable {
draw(): void;
}
class Circle implements Drawable {
radius: number;
constructor(radius: number) {
this.radius = radius;
}
draw() {
console.log(`Drawing circle with radius ${this.radius}`);
}
}
const shape: Drawable = new Circle(5);
shape.draw(); // Drawing circle with radius 5
Key Differences
- Instantiation: Classes can be instantiated with
new; interfaces cannot. - Implementation: Classes contain logic; interfaces are purely declarative.
- Inheritance: A class extends one other class but can implement multiple interfaces.
- Static Members: Only classes support static properties and methods.
Advanced Interface Features
Optional and Readonly Properties
interface Config {
host: string;
port?: number; // optional
readonly apiKey: string; // immutable after creation
}
const cfg: Config = { host: "localhost", apiKey: "abc123" };
cfg.port = 8080; // OK
// cfg.apiKey = "xyz"; // Error: readonly
Function Type Interfaces
interface BinaryOperation {
(x: number, y: number): number;
}
const multiply: BinaryOperation = (a, b) => a * b;
Index Signatures
interface Dictionary {
[key: string]: any;
}
const metadata: Dictionary = { version: "1.0", author: "dev" };
Interface Inheritance
interface Vehicle {
wheels: number;
}
interface Car extends Vehicle {
doors: number;
}
const sedan: Car = { wheels: 4, doors: 4 };
Advanced Class Features
Access Modifiers
class BankAccount {
public balance: number;
private pin: string;
protected owner: string;
constructor(balance: number, pin: string, owner: string) {
this.balance = balance;
this.pin = pin;
this.owner = owner;
}
}
const account = new BankAccount(1000, "1234", "Alex");
console.log(account.balance); // OK
// console.log(account.pin); // Error: private
Static Members
class Constants {
static GRAVITY = 9.81;
static computeWeight(mass: number) {
return mass * this.GRAVITY;
}
}
console.log(Constants.computeWeight(70)); // ~686.7
Abstract Classes
abstract class Component {
abstract render(): void;
attach() {
console.log("Component attached");
}
}
class Button extends Component {
render() {
console.log("<button>Click me</button>");
}
}
const btn = new Button();
btn.render(); // <button>Click me</button>
btn.attach(); // Component attached
Combining Classes and Interfaces
Implementing Multiple Interfaces
interface Authenticatable {
authenticate(token: string): boolean;
}
interface Cacheable {
cacheKey(): string;
}
class ApiClient implements Authenticatable, Cacheable {
authenticate(token: string) {
return token.length > 10;
}
cacheKey() {
return "api-client-v1";
}
}
Interfaces Extending Classes
class UIControl {
private enabled = true;
}
interface ClickableControl extends UIControl {
onClick(): void;
}
class Checkbox extends UIControl implements ClickableControl {
onClick() {
console.log("Checkbox toggled");
}
}
Constructor Signatures via Interfaces
interface TimerConstructor {
new (duration: number): TimerInterface;
}
interface TimerInterface {
start(): void;
}
function createTimer(
ctor: TimerConstructor,
duration: number
): TimerInterface {
return new ctor(duration);
}
class CountdownTimer implements TimerInterface {
constructor(private seconds: number) {}
start() {
console.log(`Starting countdown: ${this.seconds}s`);
}
}
const timer = createTimer(CountdownTimer, 30);
timer.start(); // Starting countdown: 30s
When to Use Classes vs Interfaces
Use classes when:
- You need to instantiate objects
- Behavior and state must be encapsulated together
- Inheritance or polymorphism is required
- Fine-grained access control (
private,protected) is needed
Use interfaces when:
- Defining APIs or contracts between modules
- Enabling loose coupling
- Validating object structure without providing logic
- Combining multiple unrelated capabilities into one type
Best Practices
- Prefer interfaces for type definitions to promote flexibility.
- Use abstract classes when sharing implementation across related types.
- Keep interfaces focused on a single responsibility.
- Avoid premature abstraction—introduce classes or interfaces only when they solve a clear design problem.