ZetCode

Interfaces and implements in Dart

last modified October 18, 2026

In this article we explore interfaces in Dart. Unlike many languages, Dart does not require a separate interface declaration: every class defines an implicit interface, and the implements keyword lets another class adopt that contract.

Interfaces Overview

An interface is a contract: a set of members that a type promises to provide. Code written against the interface can call those members without knowing the concrete implementation. In Dart, every class is an implicit interface. When class B implements class A, it promises to provide every instance member of A but inherits none of its implementations.

This is the key difference between extends and implements. extends reuses the superclass implementation; implements takes only the signature and requires the implementing class to supply every required member, either directly or through inherited implementations. Constructors and static members are not part of an implicit interface. Dart 3 also adds interface class, which can be instantiated but may only be implemented, not extended, by other libraries. Combining it with abstract prevents direct instantiation and allows abstract members; extension within the defining library is still allowed.

Tool Inherits implementation Requires reimplementation Keywords
Subclassing Yes Unimplemented abstract members in a concrete subclass extends
Implementing No Required members not already inherited implements
Mixins Yes (fields, methods, and accessors) Unimplemented abstract members; superclass constraints may apply with
Declaration-only contract No Required instance members when implemented abstract interface class

Implicit Interfaces

A class defines an interface, but modifiers such as base and final can prevent other libraries from implementing it. Here a concrete ConsoleLogger implements Logger, providing its own version of log. The declared type is Logger, so callers depend only on the contract.

implicit_interface.dart
class Logger {
  void log(String msg) => print('log: $msg');
}

class ConsoleLogger implements Logger {
  @override
  void log(String msg) => print('[console] $msg');
}

void main() {
  Logger l = ConsoleLogger();
  l.log('hello');

  var raw = Logger();
  raw.log('direct');
}

ConsoleLogger must implement log because implements does not carry the body across. The variable l is typed Logger, so the only guarantee callers have is the log signature. The original Logger class is still instantiable and keeps its own implementation, showing that a class can serve double duty as both a concrete type and an interface.

$ dart run implicit_interface.dart
[console] hello
log: direct

Implementing Multiple Interfaces

Unlike inheritance, implements is not limited to a single type. A class can implement many interfaces at once, which is how Dart expresses that an object can fulfil several independent roles.

multiple_interfaces.dart
abstract interface class Printer {
  void printDoc(String doc);
}

abstract interface class Scanner {
  void scan();
}

class MultiFunctionPrinter implements Printer, Scanner {
  @override
  void printDoc(String doc) => print('Printing: $doc');

  @override
  void scan() => print('Scanning...');
}

void main() {
  var device = MultiFunctionPrinter();
  device.printDoc('report.pdf');
  device.scan();

  Printer printer = device;
  printer.printDoc('invoice.pdf');
}

Both Printer and Scanner are declared with the abstract interface class modifier and contain only method signatures. MultiFunctionPrinter implements both, so it can be used wherever either interface is expected. Assigning it to a Printer-typed variable narrows the visible surface to just the printing contract.

$ dart run multiple_interfaces.dart
Printing: report.pdf
Scanning...
Printing: invoice.pdf

Implementing a Class Interface

Because every class is an interface, a class can implement another concrete class. The implementing type must satisfy all of that class's instance member signatures, not just its methods. Fields contribute implicit accessors: an ordinary final field requires a getter, while a mutable field requires both a getter and a setter. The implementation can use fields or explicit accessors. Private members are library-scoped and can prevent another library from providing a complete implementation.

class_interface.dart
class Point {
  final int x, y;
  const Point(this.x, this.y);

  int get sum => x + y;
}

class Point3D implements Point {
  @override
  final int x, y;
  final int z;

  const Point3D(this.x, this.y, this.z);

  @override
  int get sum => x + y + z;
}

void main() {
  Point p = const Point3D(1, 2, 3);
  print(p.sum);
  print(p.runtimeType);

  Object o = p;
  print(o is Point);
}

Point3D implements Point, so it redeclares the x and y fields and the sum getter even though Point already defines them. The overridden sum now includes z. Stored in a Point-typed variable, the object answers the sum getter; runtimeType reveals its concrete class, and an Object-typed reference still passes an is Point test.

$ dart run class_interface.dart
6
Point3D
true

Combining extends, implements, and Mixins

A single class declaration can extend one class, implement one or more interfaces, and mix in behaviours. This combination is common when a type needs to satisfy a richer contract than any single superclass provides.

combined_contracts.dart
abstract interface class Greeter {
  String greet(String name);
}

mixin FriendlyGreeting {
  String get label;

  String greet(String who) => 'Hello $who, I am a $label';
}

abstract class Shape {
  double get area;
}

class NamedCircle extends Shape with FriendlyGreeting implements Greeter {
  @override
  final String label;
  final double radius;

  NamedCircle(this.label, this.radius);

  @override
  double get area => 3.14159 * radius * radius;
}

void main() {
  var c = NamedCircle('circle', 2);
  print(c.area.toStringAsFixed(2));
  print(c.greet('Bob'));
}

NamedCircle extends Shape, so it must implement the abstract area getter. The with FriendlyGreeting clause inherits a concrete greet method, satisfying the implements Greeter contract without redeclaring that method. The label field supplies the mixin's abstract getter. The declaration order is extends, then with, then implements. Using implements FriendlyGreeting instead would adopt the mixin's interface without inheriting its method body.

$ dart run combined_contracts.dart
12.57
Hello Bob, I am a circle

extends versus implements

The practical difference is what the client inherits. Extending a class brings its implementations along; implementing a class brings only its requirements. The example below makes the contrast explicit.

extends_vs_implements.dart
class Base {
  void hello() => print('base hello');
}

class Child extends Base {}

class Clone implements Base {
  @override
  void hello() => print('clone hello');
}

void main() {
  Child().hello(); // inherited implementation
  Clone().hello(); // must supply its own
}

Child inherits hello verbatim and needs no body of its own. Clone implements Base and is therefore required to provide hello; if it omitted the method the code would not compile. Use extends to share behaviour and implements to declare capability without coupling to an implementation.

$ dart run extends_vs_implements.dart
base hello
clone hello

noSuchMethod as a Fallback

When a class implements an interface but cannot provide every member, the noSuchMethod hook can intercept the missing calls. This is common in mocks and stubs. Providing a concrete noSuchMethod override distinct from Object.noSuchMethod allows the compiler to generate forwarding methods for unimplemented interface members. Merely inheriting the default implementation from Object is not enough.

no_such_method.dart
abstract interface class Api {
  String fetch(String id);
}

class FakeApi implements Api {
  @override
  dynamic noSuchMethod(Invocation invocation) {
    if (invocation.isMethod && invocation.memberName == #fetch) {
      return 'fake:${invocation.positionalArguments.first}';
    }
    return super.noSuchMethod(invocation);
  }
}

void main() {
  var api = FakeApi();
  print(api.fetch('42'));

  Object obj = api;
  print(obj is Api);
}

FakeApi does not implement fetch directly. Instead, noSuchMethod receives the Invocation, checks the member name symbol, and returns a synthesised result from the arguments. The final is Api check, made through an Object-typed reference, still holds because FakeApi declares that it implements Api.

This is not a way to bypass the interface's types. The generated forwarder checks that the returned value matches fetch's String return type; returning null would fail at runtime. Unknown invocations are delegated to super.noSuchMethod, which throws NoSuchMethodError. Prefer explicit implementations for ordinary application code.

$ dart run no_such_method.dart
fake:42
true

Source

Dart Language: Implicit interfaces
Dart Language: Class Modifiers
Dart Language: Mixins
Dart API: Object.noSuchMethod

Interfaces in Dart are implicit: every class defines one, and implements adopts that contract without inheriting behaviour. A class can implement many interfaces, combine them with a single superclass and mixins, and use noSuchMethod for specialized mocks or proxies. Choosing between extends and implements comes down to whether you want to share an implementation or only declare a capability.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.