16.6 Implementing Interfaces with implements
Alright, let’s talk about implements. You’ve defined a beautiful, pristine interface. It’s a perfect blueprint, a contract of pure intention. Now, what? You leave it framed on the wall, a theoretical ideal? No. You build the darn thing. That’s where the implements keyword comes in. It’s your way of looking the TypeScript compiler in the eye and saying, “I swear this class will fulfill this contract. Hold me to it.”
Think of it as a strict, automated code reviewer that only cares about the public shape of your class. It doesn’t care about your clever private methods or your complex constructor logic. It just checks: “Does this class have all the properties and methods the interface demands, and are their types correct?” If the answer is yes, you get a green light. If not, it throws a very clear, often pedantic, error right in your editor. This is your first and best line of defense against your class accidentally drifting away from the interface it’s supposed to represent.
Here’s the basic, no-surprises syntax:
interface Vehicle {
make: string;
model: string;
startEngine(): boolean;
honk(sound: string): void;
}
class Car implements Vehicle {
make: string;
model: string;
isRunning: boolean; // This is fine, the class can have *more* stuff.
constructor(make: string, model: string) {
this.make = make;
this.model = model;
this.isRunning = false;
}
startEngine() {
this.isRunning = true;
return this.isRunning; // Matches the `: boolean` return type.
}
honk(sound: string) {
console.log(`${this.make} ${this.model} goes: ${sound}`);
}
}
See? We promised Vehicle and we delivered Vehicle. The compiler is happy. Notice that the class adds its own isRunning property. That’s perfectly legal. implements is about ensuring a minimum set of public members, not a maximum.
The Compiler is Your Nitpicky Friend
The beauty of implements is in its immediate, brutal feedback. Forget to add the honk method? The compiler will instantly tell you: “Class ‘Car’ incorrectly implements interface ‘Vehicle’. Property ‘honk’ is missing in type ‘Car’.” It’s fantastic. It catches these oversights now, at compile time, rather than letting them bubble up as runtime errors later. This is the core value proposition of TypeScript, and implements is a key enabler.
It’s All About the Public API
Remember, implements only checks the instance side of the class. It completely ignores the constructor and static members. This makes sense because your interface is a contract for instances, not for the class constructor itself.
interface Serializable {
serialize(): string;
}
class Document implements Serializable {
constructor(private content: string) {} // Constructor is irrelevant to `implements`
serialize() {
return this.content;
}
static fromSerialized(data: string): Document { // Static method also irrelevant
return new Document(data);
}
}
The Pitfall of Missing Properties
A common trip-up is forgetting to actually declare a property required by the interface. You might have it assigned in the constructor and think that’s enough, but it’s not. The compiler needs an explicit declaration.
class BadCar implements Vehicle {
// make: string; // <-- ERROR: Property 'make' is missing in type 'BadCar'
// model: string; // <-- ERROR: Property 'model' is missing in type 'BadCar'
constructor(make: string, model: string) {
// This assignment does NOT fulfill the interface contract.
// The class needs explicit property declarations.
this.make = make; // Error: Property 'make' does not exist on type 'BadCar'
}
// ... methods would be here
}
You can fix this by explicitly declaring the properties (make: string;) or by using parameter properties, which is the concise, canonical TypeScript way:
class GoodCar implements Vehicle {
// Declares and initializes the properties in one go. Clean.
constructor(public make: string, public model: string, private isRunning = false) {}
startEngine() {
this.isRunning = true;
return this.isRunning;
}
honk(sound: string) {
console.log(`${this.make} ${this.model} goes: ${sound}`);
}
}
Implementing Multiple Interfaces (Because Why Not?)
A class can wear many hats. You can implement multiple interfaces by separating them with commas. This is incredibly powerful for adhering to multiple, smaller contracts (a principle often called Interface Segregation).
interface Drivable {
accelerate(speed: number): void;
}
interface Lockable {
lock(): void;
}
class FancyCar implements Vehicle, Drivable, Lockable {
// ... must implement ALL members from Vehicle, Drivable, AND Lockable
make: string = 'Tesla';
model: string = 'Model S';
startEngine() { return true; }
honk(s: string) { console.log(s); }
accelerate(speed: number) { console.log(`Accelerating to ${speed} mph`); }
lock() { console.log('Doors locked.'); }
}
It’s a Design Tool, Not Just a Checkbox
Use implements proactively. Define your interfaces first. Think about the shape of the data and functionality you need. Then implement them. This forces you into a better design pattern, thinking in terms of contracts and abstractions rather than just concrete classes. It makes your code more flexible and testable. If you find a class is becoming a nightmare to implement a simple interface, that’s a fantastic signal that your class is probably doing too much and has become bloated and incohesive. The implements keyword isn’t just syntax; it’s a guide toward cleaner architecture.