Implementing Interfaces¶
Interfaces define the shape of an object.
Classes can implement interfaces to guarantee they provide specific methods and properties.
This is TypeScript’s version of enforcing contracts between components.
Enforcing Contracts¶
When a class implements an interface, TypeScript ensures the class:
- includes all required properties
- includes all required methods
- uses the correct types
- does not add incompatible definitions
Basic example¶
interface Logger {
log(message: string): void
}
class ConsoleLogger implements Logger {
log(message: string) {
console.log(`[LOG] ${message}`)
}
}
If you forget a method:
class BadLogger implements Logger {
// ❌ Error: Property 'log' is missing
}
Interfaces enforce structure, not inheritance¶
A class can implement multiple interfaces:
interface Identified {
id: string
}
interface Timestamped {
createdAt: Date
}
class User implements Identified, Timestamped {
constructor(
public id: string,
public createdAt: Date,
public name: string
) {}
}
This is more flexible than single‑inheritance class hierarchies.
Polymorphism with Types¶
Polymorphism in TypeScript is structural, not nominal.
This means:
If an object has the right shape, it can be treated as that interface.
This makes TypeScript extremely flexible.
Example: Multiple implementations of the same interface¶
interface StorageEngine {
save(key: string, value: string): void
load(key: string): string | null
}
In‑memory implementation¶
class MemoryStorage implements StorageEngine {
private store = new Map<string, string>()
save(key: string, value: string) {
this.store.set(key, value)
}
load(key: string) {
return this.store.get(key) ?? null
}
}
File‑based implementation¶
import fs from "fs"
class FileStorage implements StorageEngine {
save(key: string, value: string) {
fs.writeFileSync(key, value)
}
load(key: string) {
return fs.existsSync(key)
? fs.readFileSync(key, "utf8")
: null
}
}
Polymorphic usage¶
function run(engine: StorageEngine) {
engine.save("greeting", "Hello")
console.log(engine.load("greeting"))
}
run(new MemoryStorage())
run(new FileStorage())
The function doesn’t care which implementation it receives—only that it satisfies the interface.
This is classic polymorphism, but powered by TypeScript’s structural typing.
Interfaces + Classes + Dependency Injection¶
Interfaces make dependency injection trivial.
class App {
constructor(private storage: StorageEngine) {}
start() {
this.storage.save("status", "running")
}
}
const app = new App(new MemoryStorage())
app.start()
Swap implementations without changing the app:
const app = new App(new FileStorage())
This is how real‑world TypeScript apps achieve testability and modularity.
Interfaces with Optional & Readonly Members¶
Interfaces can define optional or readonly members that classes must respect.
interface Config {
readonly version: string
debug?: boolean
}
class AppConfig implements Config {
constructor(
public readonly version: string,
public debug?: boolean
) {}
}
TypeScript enforces:
versioncannot be reassigneddebugis optional
Structural Polymorphism (The TypeScript Twist)¶
Unlike Java or C#, a class does not need to explicitly implement an interface to be compatible with it.
Example¶
interface Point {
x: number
y: number
}
class Coordinate {
constructor(public x: number, public y: number) {}
}
const p: Point = new Coordinate(10, 20) // ✔ Allowed
The class is compatible because it has the right shape.
This is structural polymorphism, and it’s one of TypeScript’s superpowers.
Putting It All Together¶
Here’s a realistic example combining contracts, polymorphism, and structural typing:
interface PaymentProcessor {
pay(amount: number): void
}
class StripeProcessor implements PaymentProcessor {
pay(amount: number) {
console.log(`Stripe: charged $${amount}`)
}
}
class PaypalProcessor implements PaymentProcessor {
pay(amount: number) {
console.log(`PayPal: charged $${amount}`)
}
}
function checkout(processor: PaymentProcessor) {
processor.pay(50)
}
checkout(new StripeProcessor())
checkout(new PaypalProcessor())
This pattern is used everywhere:
- logging
- storage
- payment gateways
- messaging systems
- adapters and drivers
Interfaces make these systems interchangeable and testable.
Summary¶
In this lesson, you learned how TypeScript uses interfaces to enforce structure and enable polymorphism:
1. Enforcing contracts¶
- Classes must implement all required members
- Interfaces define clear, reusable APIs
- Multiple interfaces can be implemented at once
2. Polymorphism with types¶
- Structural typing enables flexible, interchangeable implementations
- Functions can accept any object matching an interface
- Great for dependency injection and testing
Interfaces + classes give you the expressive power of OOP with the flexibility of JavaScript.