Topic 6.5
Implementing Interfaces
In one line
implements asks the compiler to check that a class has everything an interface requires. It adds nothing to the class and gives it no types for free, and because TypeScript compares shapes, a class does not even need to say implements to fit an interface.
Think of it like this
A driving licence test. The rule book says a driver must be able to start, steer and brake. The examiner does not care how you learned or which car you own; they check you can do the three things. implements is that examiner: you write class Doc implements Printable and the compiler checks the class can do everything Printable lists. It is a check, not a lesson: passing the test does not teach you anything new.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Interface
- A named description of a shape: the members a value must have.
- implements
- A clause on a class asking the compiler to check it has everything the interface lists.
- Contract
- The set of promises a type makes about what it can do.
- Structural typing
- Types fit if their shapes fit, whatever they are called.
- Nominal typing
- Types fit only if they come from the same declaration or name.
- Mock
- A stand-in object with the same shape as a real one, used in tests.
- Public surface
- The members a class shows to the outside, which is all an interface can describe.
Step by step
01One class, two interfaces
Doc promises both Printable and Saveable. It has print, save and the readonly id, so the check passes. It also has a private text, an extra the interfaces don't mention, which is allowed.
interface Printable {
print(): string;
}
interface Saveable {
save(): void;
readonly id: number;
}
class Doc implements Printable, Saveable {
readonly id = 1;
constructor(private text: string) {}
print() {
return this.text;
}
save() {
console.log("saved", this.id);
}
}02Three ways to get it wrong
A missing member (printt instead of print) is TS2420 on the class name. A member with too many parameters (print(extra: number)) is TS2416 with the line "Target signature provides too few arguments". A member with the wrong return type is TS2416 again.
Every message names the class and the interface, so you know what to fix.
03It does not type your parameters
In class Impl implements Handler { handle(code) { ... } } the parameter has no type of its own, because implements is only a check. Strict mode reports TS7006: Parameter 'code' implicitly has an 'any' type. Write handle(code: number).
Extra members are fine: Extra adds bonus(). But a variable typed Handler cannot call it (TS2339), since the interface does not mention it.
04You don't even need the keyword
Duck and Robot are unrelated classes with the same quack(). chorus(d: Duck) accepts either, and an object literal too. new Robot() instanceof Duck is false: structural typing checks shapes before the program runs, while instanceof checks the prototype chain while it runs.
05Private members make a class nominal
Two classes with an identical private secret field are not interchangeable (Types have separate declarations of a private property 'secret'), and an interface with a public secret rejects a class whose secret is private. This is how a class can "brand" itself.
Try it yourself
- 1
Complete a failing class
In the second example fix
Badby renamingprintttoprint. Then fixLateso it matches (print()with no parameters), andOptsoprintreturns a string. The panel should say No type errors. - 2
Remove implements
In the first example delete
implements Printable, Saveable. Predict whether anything breaks. Then addfunction show(p: Printable) { console.log(p.print()); } show(d);.Nothing breaks:
Docstill has the right shape, soshow(d)is accepted. What you lost is the early warning when someone later renamesprintinside the class. - 3
Give the parameter its type
In the third example, change
handle(code)tohandle(code: number)inImpl. Predict what is left in the panel (only thebonuserror).
Code & diagrams
Clean: Doc has everything both interfaces list, plus a private extra. Delete save() and read the error on the class line.
Console
(nothing was printed)Expected console output
saved 1
hiThree classes, three different mistakes: a missing member, a member that wants more arguments than the interface allows, and a member with the wrong return type.
Console
(nothing was printed)The first error is the surprise: implements gave code no type. The second shows that a variable typed with the interface sees only the interface's members.
Console
(nothing was printed)No errors, and no implements anywhere. TypeScript accepts a Robot and even an object literal as a Duck. At runtime instanceof is false, because Robot is not in Duck's prototype chain.
Console
(nothing was printed)Expected console output
quack beep-quack
plain object
falseShell and Ghost look identical, but each private secret comes from its own declaration, so neither is accepted for the other, nor for an interface with a public secret.
Console
(nothing was printed)Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Misspell the member
In the "How implements fails" example, keep the class Bad that defines printt() instead of print().
Myth vs fact
Myth
A class must say implements to be used as an interface.
Fact
TypeScript is structural. Any value with the right shape is accepted. implements only adds an early, local check.
Myth
implements copies the interface's members into the class.
Fact
It adds nothing. You must write every member yourself, including parameter types.
Myth
instanceof and implements are related.
Fact
instanceof checks the prototype chain at runtime. Interfaces are erased and can't be used with instanceof at all.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
An interface can
extendsa class (interface Wide extends Shape). It then inherits the class's members, including private and protected ones, and only the class itself or its subclasses can implement it. A look-alike class getsProperty 'sides' is protected but type 'Impostor' is not a class derived from 'Shape'.Use this to build "branded" capabilities. - ▸
When a project mocks dependencies in tests, classes without private members are easier to fake: a plain object with the right methods passes. If your class has a private field, mocks need a cast or an extracted interface. This is a common reason to depend on an interface in application code and keep
implementson the real class. - ▸
Method parameters in interfaces written with the method shorthand (
print(): string) are compared bivariantly, while the property form (print: () => string) is compared strictly understrictFunctionTypes. A class that implements one or the other can differ in what it accepts (Topic 7.7 explains the two forms, Topic 16.5 the soundness holes). - ▸
implementsclauses are erased entirely. The emitted class has no trace of the interfaces, so they add no bytes and no startup cost.
Remember this
- 1
The problem this solves: A class can be expected to behave a certain way, but nothing stops it from missing a method.
implementsmakes the compiler check the class against the promised shape. An interface (Topic 3.2) describes a shape: the members a value must have. A class that implements an interface promises its instances have that shape. Writeclass Doc implements Printable, Saveableto promise several shapes at once (a class can extend only one parent, but implement as many interfaces as it likes). The interfaces are erased, so there is no runtime cost. - 2
The check happens on the class declaration. If a member is missing you get
TS2420: Class 'Bad' incorrectly implements interface 'Printable'with the missing member named underneath. If a member exists but with a wrong type, the error isTS2416on that member, with a nested explanation of why the types clash (the same shape as the override error in Topic 6.3). - 3
**
implementschanges nothing about the class.** It does not copy members in, and, an easy trap, it does not give parameters their types either. Inhandle(code) { ... }the parametercodeis not taken from the interface; it staysany, so strict mode reportsTS7006. Write the parameter types in the class yourself. The class may also have more members than the interface; the extras just are not visible through a variable typed with the interface. - 4
Interfaces describe only the public side of an instance. Properties that the interface requires must be public in the class; a
privatefield cannot satisfy an interface member. Static members and the constructor belong to the class itself and can't be described by animplementscheck (describe those with a construct signature, Topic 2.7). - 5
A class declaration also creates a type, so a class name can be used wherever an interface is wanted. And since TypeScript compares shapes and not names (Topic 3.5), you do not even need to write
implements:chorus(d: Duck)acceptsnew Robot()whenRobothas the same members asDuck. Theimplementsclause only moves the error to a better place: the class definition instead of the far-away call that uses it. - 6
Classes with
privateorprotectedmembers are the exception. Because those members come from one particular declaration, a different class (or an object literal, or an interface with a public member of the same name) can't stand in for it. This makes such classes behave like nominal types: matched by declaration and not by shape. An interface can also extend a class, which pulls in its private members too, and then only that class and its subclasses can implement it. - 7
Use
implementsas a contract at the edges of a module: repositories, services, plugins and mocks. It documents intent and fails early, while the interface itself is what the rest of your code depends on (the idea behind "program to an interface, not an implementation").
Explain it without notes
What does the implements keyword do, and what does it not do?
Why can a class that never wrote implements still be passed where an interface is expected?
How do private members change how classes are compared?
When would you use implements instead of extends?
Practice
Define interface Logger { log(msg: string): void } and two classes, ConsoleLogger and MemoryLogger (stores messages in an array), both implementing it. Write a function run(l: Logger) and call it with both.
A class Cache implements Storage2 where interface Storage2 { get(key: string): string | undefined; set(key: string, value: string): void } has a method get(key). What does tsc say, and how do you fix it?
Trade-offs
- ↔
Writing
implementscosts one line and gives early errors in the right place; leaving it out relies on far-away call sites to catch mistakes. - ↔
Giving classes private members protects internals but makes them harder to substitute in tests. Depend on interfaces in your application code to keep both.
Done when you can
I can write a class that implements one or more interfaces.
I can read
TS2420andTS2416and say what to change.I know
implementsadds no members and no parameter types.I can explain why classes with the same shape are interchangeable and how private members change that.
I can say when to choose
implementsoverextends.