skip to content

Class Modifiers

Dart 3's base, final, interface and sealed modifiers control whether code outside a library may extend or implement a class. Interviewers ask which fits an API and why sealed enables exhaustiveness.

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

explore

questions

5

In Dart 3, what do the `base`, `interface`, `final` and `sealed` class modifiers each allow code in another library to do?

level: middleimportance: must knowfreq 55%

answer

  1. two capabilities: extend, implement
  2. base keeps implementation inherited
  3. interface keeps implementation private
  4. final closes both doors
  5. sealed: final plus abstract, known subtypes

basics

~20 s

Outside the declaring library, base allows extending but not implementing, interface allows implementing but not extending, final allows neither, and sealed allows neither and is implicitly abstract so its same-library subtypes form a closed set.

solid answer

~40 s

Dart classes offer two subtyping doors to other libraries: `extends` (inherit the code) and `implements` (reuse only the type). The Dart 3 modifiers close them selectively. `base` allows `extends` but forbids `implements`, so every instance really runs your implementation, private members included. `interface` allows `implements` but forbids `extends`, so no one overrides methods your own code calls on `this`. `final` forbids both, leaving outside code only construction. `sealed` forbids both too, is implicitly `abstract`, and requires every direct subtype to live in the same library, which is what lets the compiler treat the subtypes as a closed, known set. All four restrictions are ignored inside the declaring library.

code

dart · 27 lines
dart
// payments.dart
base class PaymentMethod {
  void _audit() {}
  void charge(int cents) => _audit();
}

interface class PaymentGateway {
  void submit(int cents) {}
}

final class Receipt {
  Receipt(this.id);
  final String id;
}

// app.dart
import 'payments.dart';

base class GiftCard extends PaymentMethod {}          // OK: base may be extended
// class FakeMethod implements PaymentMethod {}      // error: base can't be implemented outside
class StubGateway implements PaymentGateway {        // OK: interface may be implemented
  @override
  void submit(int cents) {}
}
// class LoggingGateway extends PaymentGateway {}    // error: interface can't be extended outside
// class MyReceipt extends Receipt {}                // error: final can't be extended outside
final receipt = Receipt('r-1');                       // OK: final can still be constructed

go deeper

for a junior

Recall the two doors, extends and implements, and which one each of base, interface and final closes for other libraries.

for a middle

Explain the guarantee each modifier buys: base for inherited private members and constructors, interface against fragile base classes, sealed for a closed subtype set.

for a senior

Pick modifiers for a published package deliberately, knowing that adding one later is breaking and that base and final restrictions propagate to subtypes.

for a principal

Set package-wide policy on which exported types are open, interface-only or closed, balancing extensibility for users against freedom to evolve the API.

## The two doors every Dart class opens by default A plain Dart `class` can be used by any other library in four ways: **constructed**, **extended** (`extends`), **implemented** (`implements`, because every class has an implicit interface), and, if declared `mixin class`, **mixed in**. Dart 3.0 added modifiers that let a library author close the subtyping doors to *other libraries* while keeping full freedom inside the declaring library. They require a library language version of at least 3.0. Picture a payments package that exports `PaymentMethod` with subclasses `Card`, `Wallet` and `BankTransfer`. Which modifier you put on `PaymentMethod` decides what an app or another package may do with it. ## What each modifier allows outside the library | Declaration | Construct | Extend | Implement | Mix in | |---|---|---|---|---| | `class` | yes | yes | yes | no | | `base class` | yes | yes | no | no | | `interface class` | yes | no | yes | no | | `final class` | yes | no | no | no | | `sealed class` | no | no | no | no | (From the dart.dev class modifier reference; `abstract` removes construction from any row.) ## `base`: you must inherit my code `base` forbids `implements` from other libraries. The guarantees dart.dev lists: - the base class **constructor always runs** for any subtype instance; - **private members** declared in the class exist in every subtype, so code like `a._validate()` cannot fail at runtime on a foreign implementation; - adding a new concrete member does not break subtypes, since they inherit it. The price is **transitivity**: every subtype, direct or indirect, must itself be `base`, `final` or `sealed`. ## `interface`: you may implement, not inherit `interface` forbids `extends` from other libraries. It protects against the **fragile base class problem**: when one of your methods calls another on `this`, you know it runs your implementation, not a foreign override. The common form is `abstract interface class PaymentGateway { ... }`, a **pure interface** with abstract members that outside code implements. ## `final`: construct only `final` combines both restrictions: outside libraries cannot extend, implement, mix in or use it in an `on` clause. They can still construct it unless it is also `abstract`. It gives the maintainer the most freedom: you can add methods or turn a constructor into a factory without breaking anyone. Like `base`, it is transitive for subtypes declared in your own library. ## `sealed`: a closed family `sealed` also forbids extending, implementing and mixing in from other libraries, and adds two rules: 1. The sealed class is **implicitly abstract**: it cannot be constructed, though it can declare factory constructors and constructors for its subclasses. 2. Every **direct subtype** must be declared in the **same library**. Because the compiler can then see every direct subtype, it can check a switch over a `PaymentMethod` for exhaustiveness (the switch mechanics are a separate topic). Unlike `base` and `final`, `sealed` is **not transitive**: a public, unmodified subclass such as `Card` can still be extended elsewhere unless you mark it too. ## Real SDK examples `dart:core` in Dart 3.13 uses all of these: `sealed class num` (with `int` and `double` as its subtypes), `abstract final class String`, `abstract final class int`, and `abstract interface class List<E>` and `Map<K, V>`. `dart:io`'s `FileSystemEvent` became `sealed` in Dart 3.1. ## Inside the declaring library All four restrictions apply only to **other libraries** — which, in Dart, means other files, even in your own package. Within the declaring library you may extend a `final` or `interface` class and implement a `base` class, subject to the transitivity rule for `base` and `final` subtypes. ## Applying them to an existing API Adding any of these modifiers to an already published class takes a capability away from users, so dart.dev's guidance for package authors is to treat it as a **breaking change** and bump the major version.

  • In Dart 3, why does base protect code that calls private members on a parameter?
    A library-private member such as `_audit()` cannot be implemented from another library. If outside code could `implements PaymentMethod`, a call to `method._audit()` inside your library would hit an object with no such member and fail at runtime. `base` forbids outside `implements`, so every instance inherits your real `_audit`.
  • In Dart 3, can code in another library construct a sealed class or an interface class?
    An `interface class` can be constructed anywhere unless it is also `abstract`. A `sealed class` never can: it is implicitly abstract, though it may declare factory constructors that return one of its subclasses.
  • In Dart 3, is putting final on an already published class a breaking change?
    Yes. Any user who extended or implemented it now gets a compile error, so dart.dev advises a major version bump for adding `final`, `base`, `interface` or `sealed` to an existing public class.

saying these in an interview costs you the question

  • final on a class means its fields are immutable.
  • interface class declares a type with no method bodies allowed.
  • base prevents other libraries from extending the class.
  • sealed classes can be constructed like final classes.
  • The modifiers also restrict subtyping inside the declaring library.
open as a page

In Dart 3, why can you extend a `final` class from the same file but not from another file in the same package?

level: juniorimportance: should knowfreq 35%

basics

~20 s

Class modifiers restrict other libraries, and in Dart a library is one file plus its part files, not a package. A second file in the same package is a separate library, so it cannot extend or implement a final class.

open as a page

In Dart, when should a payment-method hierarchy be `sealed` rather than `final`, and what does each cost when you later add a method?

level: middleimportance: should knowfreq 45%

basics

~20 s

Use sealed when PaymentMethod is abstract with a fixed set of same-library subtypes and callers should switch over them exhaustively; adding a subtype is then breaking. Use final when the type must be constructible or new subtypes must be non-breaking.

open as a page

In Dart 3, if `PaymentMethod` is a `base` class, why must every subclass be `base`, `final` or `sealed`, and does the same apply under `interface` or `sealed`?

level: seniorimportance: should knowfreq 30%

basics

~20 s

base guarantees every instance inherits the real implementation, so a subclass that allowed implements would reopen the hole; hence subtypes of base or final must be base, final or sealed. interface and sealed do not propagate.

open as a page

In Dart 3, what do `abstract interface class` and `abstract final class` declare, and which class modifier combinations does the compiler reject?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

abstract interface class is Dart's pure interface: implementable elsewhere, never extended or constructed. abstract final class cannot be instantiated directly or subtyped outside its library, suiting static-only holders. Rejected: two of base/interface/final, sealed with any of them or abstract, and mixin with interface, final or sealed.

open as a page