skip to content

implements vs extends & Interface Gaps

Why implements is only a conformance check while extends actually inherits, and the surprising gaps that leaves — no inference for member types, optional members satisfied by nothing, private members making a class effectively nominal. Interviewers ask it to see whether you know a class is both a value and a type.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

questions

5

In TypeScript, what is the difference between `class Duck implements Bird` and `class Duck extends Bird` — what does each clause change about type checking and about the emitted JavaScript?

level: juniorimportance: must knowfreq 78%

answer

  1. one is a check, one is inheritance
  2. nothing is emitted for it
  3. no method bodies come along
  4. many interfaces, only one base class

basics

~20 s

extends is real inheritance: the base class's members come along and the clause survives into the emitted JavaScript. implements is a compile-time-only conformance check that copies nothing and is erased. A class may implement many interfaces but extend only one class.

solid answer

~40 s

`extends` is real inheritance: a class gets exactly one base class, the base's members come along, and the clause survives into the emitted JavaScript, so instances genuinely reach the base's methods at runtime. `implements` is a TypeScript-only assertion — the compiler checks that the class's instance type is assignable to the named type, then erases the clause completely; delete it and the output is identical. Because it is only a check, it copies no method bodies, adds nothing to the class's type, and supplies no parameter types to the methods you write. A class can implement any number of interfaces but extend only one class. In practice I reach for `implements` to catch drift at the definition site instead of at every call site, and for `extends` only when I actually want shared behaviour.

code

typescript · 26 lines
typescript
interface Flyer {
  fly(): string;
}

class Bird {
  wings = 2;
  fly(): string {
    return "flap";
  }
}

// extends: members are inherited and the clause survives compilation
class Duck extends Bird {
  swim(): string {
    return "paddle";
  }
}

// implements: only a check — fly() must be written out here
class Kite implements Flyer {
  fly(): string {
    return "glide";
  }
}

console.log(new Duck().fly(), new Duck().wings, new Kite().fly());

go deeper

for a junior

Be ready to say plainly that extends inherits real members from one base class while implements only checks a shape and disappears at compile time, and that you can list several interfaces after implements.

for a middle

Explain the mechanics: the class type is built from its own body and then compared against the implements clause, so no bodies, no members and no parameter types flow in from the interface, and the emitted JavaScript is unchanged.

for a senior

Show why you still write implements despite it adding nothing — it moves a shape error from scattered call sites to the declaration — and be able to name the gaps it silently leaves in real code reviews.

for a principal

Own the API-shape decision: contracts cost nothing after erasure and let unrelated types satisfy them, while a base class buys reuse at the price of permanent coupling every subclass inherits when the base changes.

## The one-line difference `extends` on a class is a JavaScript feature that TypeScript additionally type-checks. `implements` is a TypeScript-only assertion that the compiler verifies and then throws away. Everything else follows from that. ## What extends does A class may extend exactly one other class. The base's members become members of the derived class in the type system *and* in the output: the emitted JavaScript still reads `class Duck extends Bird`, so instances really do reach the base's methods at run time (how that lookup works is a JavaScript question — here the point is only that it survives compilation). Type-side consequences: - `Duck` is assignable wherever `Bird` is expected. - You can call inherited members you never wrote in `Duck`. - A member you redeclare must stay compatible with the one it replaces, or the compiler errors. - A derived constructor must call `super()` before using `this`. ```ts class Bird { wings = 2; fly(): string { return "flap"; } } class Duck extends Bird { swim(): string { return "paddle"; } } new Duck().fly(); // "flap" — inherited, never written in Duck ``` ## What implements does `class Duck implements Bird` says exactly one thing: *check that Duck's instance type is assignable to Bird*. That is the whole feature, and every gap people trip over is a consequence of it. - **It copies nothing.** If the interface declares `fly(): string`, you write `fly` yourself. An interface has no bodies to inherit; there is nothing to copy even in principle. - **It emits nothing.** The clause is erased. Compare the JavaScript with and without it and they are byte-identical, which is why a stale `implements` can never break at run time — only at compile time. - **It does not change the class's type.** The class's type is built from its own body, then compared to the interface. Members the interface declares but the class did not write (optional ones) do not appear on the class. - **It does not feed inference.** Parameter types of the methods in the class body are not taken from the interface; you annotate them yourself. - **You may list several.** `class Duck implements Bird, Swimmer {}` is legal; `extends` accepts a single class. - **The right-hand side need not be an interface.** A type alias for an object type works, and so does another class — in which case only that class's instance shape is used, and, again, nothing is inherited. ```ts interface Flyer { fly(): string; } interface Swimmer { swim(): string; } class Duck implements Flyer, Swimmer { fly(): string { return "flap"; } // must be written out swim(): string { return "paddle"; } // must be written out } ``` ## Why write implements at all, then? Since it adds nothing, its only value is *where the error appears*. Without the clause, a class that has drifted out of shape still compiles fine on its own and fails at the call sites that pass it somewhere expecting the contract — potentially dozens of them, in other files, with confusing messages. With the clause, the failure lands on the class declaration, next to the code you have to fix. It is a fence, not a feature. The same idea shows up elsewhere in the language: annotating a variable with the contract type, or checking a value against a type without widening it, both pin the error to the definition. `implements` is the class-shaped version of that habit. ## Choosing between them Use `extends` when you genuinely want to share implementation and accept the coupling that comes with it: the derived class is tied to the base's members, and later changes to the base reach every subclass. Use `implements` when you only want to guarantee a shape — several unrelated classes can satisfy the same contract, a class can satisfy several contracts, and no code travels between them. The broader design argument about inheritance versus composition is language-agnostic; what TypeScript contributes is that the contract half of it costs literally nothing at run time, because the clause is erased. A common trap in review is treating `implements` as a weaker `extends`. It is not weaker — it is a different kind of statement. One moves code; the other only asks a question and reports the answer.

  • Can a class `implements` another class rather than an interface, and what does that check?
    Yes. The compiler uses the named class's *instance* type as the contract and checks assignability — nothing is inherited, so you still write every member yourself. It is occasionally useful for "same shape, unrelated lineage" types, but it breaks down the moment the referenced class has private or protected members, because those can only ever be satisfied by that class's own family.
  • If `implements` produces no code and adds nothing to the class, why not just drop it everywhere?
    Because it decides where the error surfaces. Without it, a class that no longer matches the contract compiles happily and fails at every place it gets passed to something expecting that contract, often in other files. With it, the failure appears on the class declaration itself, which is the code you have to change. The cost is zero — it is erased.
  • What happens to an `implements` clause that names an interface which no longer exists at run time?
    Nothing happens, because interfaces never exist at run time. Types are erased during compilation: the interface emits no code, and the clause emits no code. It is purely a compile-time relationship, which is why a bad `implements` is always a build failure and never a production error.

implements is a compliance inspection: an inspector checks the product against the spec sheet, signs off, and leaves nothing behind. extends is assembling your product out of another product's parts, which stay in the box after you ship.

saying these in an interview costs you the question

  • Thinks implements inherits the interface's default implementations
  • Says a class can only implement one interface
  • Believes the implements clause emits a runtime check
  • Calls extends "implements with extra checking"
  • Assumes implements adds the interface's members to the class

context

open as a page

In TypeScript, given `interface Checker { check(name: string): boolean }` and `class NameChecker implements Checker { check(s) { return s.length > 0; } }`, why does the compiler complain about `s` when `noImplicitAny` is on?

level: middleimportance: should knowfreq 46%

basics

~20 s

An implements clause never supplies types. The class type is built from the class body first and only then compared to the interface, so the unannotated parameter is an implicit any — an error under noImplicitAny. Annotate it yourself.

open as a page

In TypeScript, `interface Options { retries: number; onError?: () => void }` and `class Config implements Options { retries = 3 }` — does that compile, and what goes wrong later when code calls `config.onError?.()`?

level: middleimportance: should knowfreq 38%

basics

~20 s

It compiles: an optional member is satisfied by absence. But the implements clause adds nothing to the class, so the type Config has no onError at all, and reading it through a Config-typed value is a compile error. Declare it explicitly.

open as a page

In TypeScript, a `class User {}` declaration puts two different things into scope under the same name. What are they, and what does `typeof User` mean when written in a type position?

level: middleimportance: should knowfreq 52%

basics

~20 s

A class declaration creates a value — the constructor object, holding the static members — and a type of the same name meaning an instance. In a type position, typeof User refers to that class value itself; InstanceType<typeof User> gets back the instance type.

open as a page

In TypeScript, what does `interface Handle extends Connection {}` mean when `Connection` is a class with a private field, and which types can then satisfy `Handle`?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

An interface may extend a class type: it inherits the member types, including private and protected ones, but no implementations. Because private members are compared by declaration site, only that class and its subclasses can produce a type assignable to the interface.

open as a page