Command Palette

Search for a command to run...

Hectal
PHASE 6Intermediate ~20 min· topic 5 of 8

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.

doc.tswhole filets
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.

You don't even need the keyworddiagram
Rendering diagram…

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. 1

    Complete a failing class

    In the second example fix Bad by renaming printt to print. Then fix Late so it matches (print() with no parameters), and Opt so print returns a string. The panel should say No type errors.

  2. 2

    Remove implements

    In the first example delete implements Printable, Saveable. Predict whether anything breaks. Then add function show(p: Printable) { console.log(p.print()); } show(d);.

    Nothing breaks: Doc still has the right shape, so show(d) is accepted. What you lost is the early warning when someone later renames print inside the class.

  3. 3

    Give the parameter its type

    In the third example, change handle(code) to handle(code: number) in Impl. Predict what is left in the panel (only the bonus error).

Code & diagrams

A class that implements two interfaces

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
hi
How implements fails

Three 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)
implements does not type parameters or hide extras

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)
Classes are types: shape decides

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
false
Private members make classes nominal

Shell 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().

terminal
$ Types panel
── what you'll see ──
index.ts(4,7): error TS2420: Class 'Bad' incorrectly implements interface 'Printable'.
Property 'print' is missing in type 'Bad' but required in type 'Printable'.

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 extends a 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 gets Property '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 implements on 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 under strictFunctionTypes. 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).

  • ▸

    implements clauses are erased entirely. The emitted class has no trace of the interfaces, so they add no bytes and no startup cost.

Remember this

  1. 1

    The problem this solves: A class can be expected to behave a certain way, but nothing stops it from missing a method. implements makes 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. Write class Doc implements Printable, Saveable to 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. 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 is TS2416 on that member, with a nested explanation of why the types clash (the same shape as the override error in Topic 6.3).

  3. 3

    **implements changes nothing about the class.** It does not copy members in, and, an easy trap, it does not give parameters their types either. In handle(code) { ... } the parameter code is not taken from the interface; it stays any, so strict mode reports TS7006. 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. 4

    Interfaces describe only the public side of an instance. Properties that the interface requires must be public in the class; a private field cannot satisfy an interface member. Static members and the constructor belong to the class itself and can't be described by an implements check (describe those with a construct signature, Topic 2.7).

  5. 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) accepts new Robot() when Robot has the same members as Duck. The implements clause only moves the error to a better place: the class definition instead of the far-away call that uses it.

  6. 6

    Classes with private or protected members 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. 7

    Use implements as 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

01

What does the implements keyword do, and what does it not do?

02

Why can a class that never wrote implements still be passed where an interface is expected?

03

How do private members change how classes are compared?

04

When would you use implements instead of extends?

Practice

01

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.

02

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 implements costs 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 TS2420 and TS2416 and say what to change.

  • I know implements adds 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 implements over extends.