TypeScript implements Keyword

The implements keyword lets a class declare that it satisfies the shape described by one or more interfaces. Once you write class Foo implements Bar, the TypeScript compiler checks every member Bar requires and reports an error if Foo is missing any of them or gets a type wrong. It is a compile-time contract only — it adds no runtime behavior, no inherited method bodies, and no runtime type check. Understanding exactly what it checks (and what it doesn’t) is essential for writing predictable, well-typed object-oriented TypeScript.

Overview / How it works

An interface describes the shape of an object: which properties and methods it must have, and what their types are. It contains no implementation. When a class uses implements InterfaceName, you are telling the compiler: “verify that instances of this class conform to that shape.” The compiler then performs structural checking — it doesn’t care about the class’s name or where it was declared, only whether every required member is present with a compatible type.

This is different from extends, which is used for class-to-class inheritance and actually copies down implementation (method bodies, field initializers) from a base class. implements copies down nothing: every method the interface declares must be written out, in full, inside the implementing class. If you want to share actual code between classes, you need extends (or composition); implements only shares a type contract.

A class can implement more than one interface at once, separated by commas: class Foo implements A, B. In that case the class must satisfy the union of both interfaces’ required members. A class can also both extends a base class and implements one or more interfaces at the same time.

Crucially, interfaces (and the implements clause itself) exist purely in the type system. During compilation to JavaScript, TypeScript erases all type information — interfaces are deleted entirely, and implements ClauseName is stripped from the class declaration. There is nothing left at runtime to inspect; you cannot do if (obj instanceof SomeInterface) because an interface has no runtime representation at all.

Syntax

interface InterfaceName {
  propertyName: PropertyType;
  methodName(param: ParamType): ReturnType;
}

class ClassName implements InterfaceName {
  propertyName: PropertyType;
  methodName(param: ParamType): ReturnType {
    // implementation required here
  }
}

// implementing more than one interface
class ClassName2 implements InterfaceA, InterfaceB {
  // must satisfy every member from both interfaces
}
Part Meaning
interface InterfaceName { ... } Declares the shape (properties, methods, optional/readonly members) a conforming object must have.
implements InterfaceName Placed after the class name (and after any extends clause); tells the compiler to check the class against that shape.
implements A, B A class may satisfy multiple interfaces at once, comma-separated.
Required members Every non-optional property and method in the interface must exist on the class with a compatible (assignable) type.
Optional members (name?: T) May be omitted by the implementing class, or provided as T | undefined.
readonly members The interface can mark a property readonly; the class’s matching property does not have to be readonly itself, but it must be assignable and shouldn’t be reassigned if you want to honor the contract.

Examples

Example 1: A basic contract

interface Vehicle {
  topSpeed: number;
  start(): void;
  stop(): void;
}

class Car implements Vehicle {
  topSpeed: number;

  constructor(topSpeed: number) {
    this.topSpeed = topSpeed;
  }

  start(): void {
    console.log("Car starting");
  }

  stop(): void {
    console.log("Car stopping");
  }
}

const car = new Car(220);
car.start();
console.log(`Top speed: ${car.topSpeed} km/h`);
car.stop();
Output:
Car starting
Top speed: 220 km/h
Car stopping

The Vehicle interface requires a topSpeed property and two methods. Car declares all three with compatible types, so it type-checks. If Car renamed topSpeed or changed its type to a string, the compiler would reject the class immediately, before any code ever runs.

Example 2: Implementing multiple interfaces

interface Printable {
  print(): void;
}

interface Serializable {
  serialize(): string;
}

class Report implements Printable, Serializable {
  constructor(private title: string, private body: string) {}

  print(): void {
    console.log(`${this.title}\n${this.body}`);
  }

  serialize(): string {
    return JSON.stringify({ title: this.title, body: this.body });
  }
}

const report = new Report("Q1 Sales", "Revenue increased 12%.");
report.print();
console.log(report.serialize());
Output:
Q1 Sales
Revenue increased 12%.
{"title":"Q1 Sales","body":"Revenue increased 12%."}

Report satisfies two independent interfaces at once. Notice that the interfaces only describe method signatures — the constructor parameters (title, body) belong to the class itself and aren’t dictated by either interface. This is a common, useful pattern: small, focused interfaces (sometimes called “role interfaces”) that a class can mix and match as needed.

Example 3: Optional properties and a realistic model

interface Employee {
  readonly id: number;
  name: string;
  department?: string;
  getSummary(): string;
}

class FullTimeEmployee implements Employee {
  readonly id: number;
  name: string;
  department?: string;
  private salary: number;

  constructor(id: number, name: string, salary: number, department?: string) {
    this.id = id;
    this.name = name;
    this.salary = salary;
    this.department = department;
  }

  getSummary(): string {
    const dept = this.department ? ` in ${this.department}` : "";
    return `${this.name} (#${this.id})${dept} earns $${this.salary.toLocaleString()}`;
  }
}

const emp = new FullTimeEmployee(101, "Priya Shah", 95000, "Engineering");
console.log(emp.getSummary());
Output:
Priya Shah (#101) in Engineering earns $95,000

Here department is optional in the interface (department?: string), so the class may or may not receive it, and salary is a private field that exists purely on the class — the interface says nothing about it, since interfaces only constrain the members they explicitly list.

How it works step by step / Under the hood

  • Step 1 — collect required members: the compiler reads the interface (and any interfaces it extends) and builds a list of required and optional members with their types.
  • Step 2 — structural comparison: for every required member, the compiler checks that the class has a member of the same name whose type is assignable to the interface’s declared type. Extra members on the class that aren’t in the interface are completely fine.
  • Step 3 — error reporting: if any required member is missing, or present with an incompatible type, TypeScript reports it as “Class incorrectly implements interface” at the class declaration, listing exactly which member(s) are the problem.
  • Step 4 — erasure at compile time: once type-checking passes, the TypeScript compiler emits plain JavaScript. The implements Vehicle clause, and the interface Vehicle { ... } declaration itself, are both deleted — they produce zero bytes of output JS. The compiled Car class in Example 1 becomes an ordinary ES class with a constructor and two methods; nothing in the emitted code says it ever implemented anything.
  • Step 5 — no runtime enforcement: because interfaces don’t exist at runtime, nothing stops you from later mutating an object so it no longer satisfies the interface it was checked against when created (for example, deleting a property via bracket-notation hacks). The guarantee implements gives you is strictly a compile-time one.

Common Mistakes

Mistake 1: Forgetting to implement every required member

interface Shape {
  area(): number;
  perimeter(): number;
}

class Square implements Shape {
  constructor(private side: number) {}

  area(): number {
    return this.side * this.side;
  }
}

This fails to compile with an error similar to: Class 'Square' incorrectly implements interface 'Shape'. Property 'perimeter' is missing in type 'Square' but required in type 'Shape'. The interface requires both area() and perimeter(), but the class only defines one. The fix is to implement every required member:

interface Shape {
  area(): number;
  perimeter(): number;
}

class Square implements Shape {
  constructor(private side: number) {}

  area(): number {
    return this.side * this.side;
  }

  perimeter(): number {
    return this.side * 4;
  }
}

const sq = new Square(5);
console.log(`Area: ${sq.area()}, Perimeter: ${sq.perimeter()}`);
Output:
Area: 25, Perimeter: 20

Mistake 2: Trying to implement a non-object type alias

type ID = string | number;

// Error: A class can only implement an object type
// or intersection of object types with statically known members.
class User implements ID {
  value: string | number = "";
}

implements only works with types that describe an object’s shape — interfaces, object type aliases, or intersections of them. A union like string | number has no fixed set of members, so there’s nothing for the class to structurally satisfy, and tsc rejects it outright. Use an object type (or interface) instead:

type Identifiable = {
  id: string | number;
};

class User implements Identifiable {
  id: string | number;

  constructor(id: string | number) {
    this.id = id;
  }
}

const u = new User("abc123");
console.log(u.id);
Output:
abc123

A related, more conceptual trap: developers sometimes assume implements behaves like extends and will provide default method bodies. It never does — implements is a type-only contract, so every method listed in the interface must be written out in the class, even if the logic is identical across several implementing classes. If you want shared logic, use a base class with extends, or extract a helper function/utility the classes both call.

Best Practices

  • Keep interfaces small and focused (one capability each) rather than one giant interface — classes can implement several small interfaces, which keeps contracts easy to read and reuse.
  • Use implements to catch mistakes early: if you intend a class to match a public API shape, adding implements turns a silent mismatch into a compile-time error.
  • Don’t duplicate a type by writing both an interface and a near-identical class shape by hand — let the interface be the single source of truth for the contract, and let the class type be inferred from its members.
  • Remember interfaces carry no runtime information; if you need a runtime check (e.g. to distinguish objects at runtime), use a discriminant property, a class instance check with instanceof against an actual class, or a user-defined type guard — never instanceof SomeInterface.
  • When a class needs both shared implementation and a shape contract, combine extends and implements: class Foo extends Base implements SomeInterface.
  • Prefer readonly in an interface for properties that shouldn’t change after construction, and mirror that with readonly on the implementing class’s field to actually enforce immutability at the class level too.

Practice Exercises

  • Define an interface Comparable with a single method compareTo(other: this): number. Create a class Money that stores an amount in cents and implements Comparable, returning a negative, zero, or positive number depending on which amount is larger.
  • Define an interface Logger with methods info(message: string): void and error(message: string): void. Write a class ConsoleLogger that implements it by prefixing messages with [INFO] or [ERROR] before printing. Then deliberately remove the error method and note the exact compiler error you get.
  • Define two interfaces, Flyable (with fly(): void) and Swimmable (with swim(): void). Create a class Duck that implements both, and a class Airplane that implements only Flyable. Write a function that accepts anything typed Flyable and calls fly() on it, then call that function with both a Duck and an Airplane instance.

Summary

  • implements makes the compiler verify a class contains every member an interface (or object type alias) requires, with compatible types.
  • It is purely a compile-time contract: interfaces and implements clauses are erased entirely from the emitted JavaScript, and there is no runtime check.
  • Unlike extends, implements shares no implementation — every method must be written out in the implementing class.
  • A class can implement multiple interfaces at once (comma-separated) and can combine extends with implements.
  • Only object types (interfaces, object type aliases, intersections of these) can be implemented — union types and primitives cannot.
  • Optional (?) and readonly interface members give the implementing class flexibility while still enforcing the overall shape.