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.
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.
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 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.
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.
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.
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
List all Dart tutorials.