Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Dart (3.13)  Module 9 – Inheritance, mixins, class modifiers and extensions

Class modifiers: base, interface, final, sealed

Control how other libraries use your Dart classes with base, interface, final and sealed modifiers, added in Dart 3.0 to protect API boundaries.

  • Advanced
  • 10 minutes
  • Examples run with dart 3.13.5 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 4 of 10 minutes free
Free: Modifiers limit what other libraries may do · Inside its own library a class can be extended and implemented. With a pass: 2 in-depth parts and 3 interview answers, about 6 minutes.

What you will learn

  • Predict which class modifier prevents a given subtype relationship
  • Explain why a subtype of a base class must itself be base, final, or sealed
  • Choose between final and sealed when closing a type hierarchy
  • Plan how adding a modifier to a published class affects its users

Before you start

What you’ll learn · Before you start

What you will learn

  • Predict which class modifier prevents a given subtype relationship
  • Explain why a subtype of a base class must itself be base, final, or sealed
  • Choose between final and sealed when closing a type hierarchy
  • Plan how adding a modifier to a published class affects its users

Before you start

On this page

A library exports a class that six other packages implement. When the library adds a new method, every implementer breaks: the method is missing. Before Dart 3.0, the library author could only hope users noticed the documentation. Since Dart 3.0, class modifiers let you say what other libraries may do with your class, and the compiler enforces the rule.

Four modifiers control subtyping across library boundaries: base class: A class marked base that can be extended but not implemented outside its library; subtypes must also be base, final, or sealed. allows extending but not implementing, interface class: A class marked interface that can be implemented but not extended outside its library, useful when you want users to provide their own implementations. allows implementing but not extending, final class: A class marked final that cannot be extended or implemented outside its library, closing the type hierarchy entirely. closes the hierarchy entirely, and sealed closes it while enabling exhaustive switches. This lesson shows how each modifier protects an API boundary, which combinations compile, and how to migrate a published class without breaking its users. Every error message on this page comes from Dart 3.13.5.

Modifiers limit what other libraries may do

A class without a modifier is open: code outside its library can construct it, extend it, implement it and, if it is also a mixin class, mix it in. Each modifier removes one or more of those abilities for code outside the library. Inside the same library extending and implementing stay open, with two rules the next section shows.

Here is a small payments library with one class of each kind, and a client file that uses them:

payments.dart: four classes with four modifiers Dart · modifiers/payments.dart
/// A small payments library showing class modifiers.
library payments;

/// A final class: cannot be extended or implemented outside its library.
final class Money {
  final int paise;
  const Money(this.paise);
  
  Money operator +(Money other) => Money(paise + other.paise);
  
  @override
  String toString() => 'Money($paise paise)';
}

/// An interface class: can be implemented but not extended outside its library.
interface class Gateway {
  bool process(Money amount) {
    print('Gateway processed $amount');
    return true;
  }
}

/// A base class: can be extended but not implemented outside its library.
/// Subtypes must be base, final, or sealed.
base class Ledger {
  final List<Money> _entries = [];
  
  void record(Money amount) {
    _entries.add(amount);
    print('Ledger recorded $amount');
  }
  
  Money get total => _entries.fold(Money(0), (a, b) => a + b);
}

/// A sealed class: cannot be extended or implemented outside its library.
/// The compiler knows all direct subtypes (for exhaustive switches).
sealed class PaymentResult {}

class Success extends PaymentResult {
  final Money amount;
  Success(this.amount);
}

class Failure extends PaymentResult {
  final String reason;
  Failure(this.reason);
}
app.dart: a client that uses the library Dart · modifiers/app.dart
/// Client code using the payments library with class modifiers.
import 'payments.dart';

/// This compiles: Gateway can be implemented (interface class).
class TestGateway implements Gateway {
  @override
  bool process(Money amount) {
    print('TestGateway processed $amount');
    return true;
  }
}

/// This compiles: Ledger can be extended (base class).
/// The subtype must be marked base, final, or sealed.
base class AppLedger extends Ledger {
  @override
  void record(Money amount) {
    print('[app] Recording $amount');
    super.record(amount);
  }
}

void main() {
  // Money can be constructed (final allows construction).
  final payment = Money(10000);
  print('Payment: $payment');
  
  // Gateway can be constructed and implemented.
  final gateway = TestGateway();
  gateway.process(payment);
  
  // Ledger can be extended.
  final ledger = AppLedger();
  ledger.record(Money(1000));
  ledger.record(Money(2500));
  print('Total: ${ledger.total}');
}

Output

Payment: Money(10000 paise)
TestGateway processed Money(10000 paise)
[app] Recording Money(1000 paise)
Ledger recorded Money(1000 paise)
[app] Recording Money(2500 paise)
Ledger recorded Money(2500 paise)
Total: Money(3500 paise)

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run app.dart

The client constructed a Money, implemented a Gateway, and extended a Ledger, all of which the modifiers allow. The table below summarises what each modifier permits from another library:

Modifier Construct Extend Implement Mix in
(none) yes yes yes no
base yes yes no no
interface yes no yes no
final yes no no no
sealed no no no no
abstract no yes yes no
mixin no no yes yes

A class modifier: A keyword before a class declaration that controls what other libraries may do with it: base, interface, final, sealed, abstract or mixin. controls the relationship between a class and code in other libraries. The modifier’s name hints at its effect: base forces other code to build on your code as a base, keeping your implementation; interface lets other code match your interface without inheriting your implementation; final ends the hierarchy; sealed closes it for exhaustive switching.

What happens when client code tries something a modifier forbids? The compiler refuses to build it:

final_extend_fails.dart: cannot extend a final class Dart · modifiers/final_extend_fails.dart
/// A final class cannot be extended outside its library.
import 'payments.dart';

class ExtendMoney extends Money {
  ExtendMoney(super.paise);
}

void main() {}

Output (exit status 254)

Printed as an error (standard error)

final_extend_fails.dart:4:27: Error: The class 'Money' can't be extended outside of its library because it's a final class.
class ExtendMoney extends Money {
                          ^

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run final_extend_fails.dart

interface_extend_fails.dart: cannot extend an interface class Dart · modifiers/interface_extend_fails.dart
/// An interface class cannot be extended outside its library.
import 'payments.dart';

class ExtendGateway extends Gateway {
  @override
  bool process(Money amount) => true;
}

void main() {}

Output (exit status 254)

Printed as an error (standard error)

interface_extend_fails.dart:4:29: Error: The class 'Gateway' can't be extended outside of its library because it's an interface class.
class ExtendGateway extends Gateway {
                            ^

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run interface_extend_fails.dart

base_implement_fails.dart: cannot implement a base class Dart · modifiers/base_implement_fails.dart
/// A base class cannot be implemented outside its library.
import 'payments.dart';

base class FakeLedger implements Ledger {
  @override
  void record(Money amount) {}
  
  @override
  Money get total => Money(0);
}

void main() {}

Output (exit status 254)

Printed as an error (standard error)

base_implement_fails.dart:4:34: Error: The class 'Ledger' can't be implemented outside of its library because it's a base class.
base class FakeLedger implements Ledger {
                                 ^

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run base_implement_fails.dart

Each message names the modifier and what it forbids, and each says “outside of its library”: the next section shows what changes inside it.

Checkpoint

Check: What each modifier permits

  1. One question A library exports interface class Gateway. Which operation is allowed in another library?

    Choose one answer.

    Show the answer: One question

    Answer: Implementing Gateway

    The interface modifier allows code in other libraries to implement the class but not extend it. This lets users create their own implementations of the interface while preventing them from inheriting the class's code.

Inside its own library a class can be extended and implemented

The limits on extending and implementing apply only to other libraries: code in the same library may extend or implement a class whatever its modifier. This matters because a sealed class is usually declared next to its subtypes, and those subtypes must be able to extend or implement it.

In payments.dart above, Success and Failure both extend the sealed class PaymentResult, which is legal because they are in the same library. Move Success to a separate library and it fails: the class can’t be extended outside its library because it is sealed. The sealed class: A class marked sealed: it cannot be extended or implemented outside its own library, so the compiler knows all its direct subtypes and can check that a switch over them is exhaustive. More in “Validating JSON in Dart with patterns” modifier is covered in detail in Sealed classes and exhaustive switches.

Two rules hold inside the library too: a subtype of a base or final class must itself be base, final or sealed, and only a mixin or a mixin class may follow with. This file declares three modified classes and five subtypes:

same_library_fails.dart: five subtypes beside the classes they extend Dart · modifiers/same_library_fails.dart
/// One library that builds on its own modified classes.
/// The first three subtypes compile; the last two are refused even here.
final class Money {
  final int paise;
  const Money(this.paise);
}

interface class Gateway {
  bool process(Money amount) => amount.paise > 0;
}

sealed class PaymentResult {}

// Allowed in the declaring library: extend an interface class, extend a sealed class,
// and extend a final class with a subtype that is itself final.
class LoggingGateway extends Gateway {}

class Success extends PaymentResult {}

final class Rupees extends Money {
  const Rupees(int rupees) : super(rupees * 100);
}

// Refused in the declaring library too: a subtype of a final class without base, final or sealed,
// and a class that is not a mixin used after with.
class Token extends Money {
  const Token(super.paise);
}

class Audited with Gateway {}

void main() {}

Output (exit status 254)

Printed as an error (standard error)

same_library_fails.dart:26:7: Error: The type 'Token' must be 'base', 'final' or 'sealed' because the supertype 'Money' is 'final'.
Try adding 'base', 'final', or 'sealed' to the type.
class Token extends Money {
      ^
same_library_fails.dart:30:20: Error: The class 'Gateway' can't be used as a mixin because it isn't a mixin class nor a mixin.
class Audited with Gateway {}
                   ^

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run same_library_fails.dart

The compiler accepted three: LoggingGateway extends an interface class, Success a sealed class, and Rupees the final Money while being final itself. It refused Token at line 26, which extends Money with no modifier, and Audited at line 30, which puts a class that is not a mixin after with.

Key idea

A class modifier sets what other libraries may do. Inside your own library any class may be extended and implemented, but a subtype of a base or final class must still be base, final or sealed.

Checkpoint

Check: What still applies inside the declaring library

  1. Question 1 of 2 A library file contains final class Money and another class Rupee extends Money. Does this compile?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: No: Rupee must be marked base, final or sealed, even in the same library

    A final class may be extended inside its own library, but every subtype of a final or base class must itself be marked base, final or sealed, in any library. So class Rupee extends Money fails, and final class Rupee extends Money compiles.

  2. Question 2 of 2 Which statement is true about class modifiers inside their own library?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A subtype of a base class must be marked base, final or sealed even there

    Inside the declaring library, extending and implementing are open whatever the modifier, but base transitivity and the rule that only a mixin may follow with still apply there.

Rules that spread to subtypes and combinations that are refused Pro: opens with a Pro, Premium or Ultimate pass

This part covers base transitivity, why base protects private members, the difference between interface and abstract interface class, using final for value types, and which modifier combinations the language allows. Where the compiler refuses something, the section shows its exact message from Dart 3.13.5 and the reason for the rule.

In this part:

  • Base transitivity: subtypes inherit the restriction
  • Interface versus abstract interface class
  • Final class for value types
  • Combinations the language refuses

2 runnable examples (Dart) · about 7 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Evolving a library API without breaking users Pro: opens with a Pro, Premium or Ultimate pass

Adding a modifier to a published class is a breaking change: client code that was allowed before becomes a compile error. This part shows how to deprecate a subtype relationship before removing it, the deprecation constructors added in Dart 3.10, and how to plan a migration with semantic versions.

In this part:

  • Adding a modifier is a breaking change
  • Deprecate first, remove later
  • The SDK’s own examples: IOOverrides and RegExp
  • Plan the migration

4 runnable examples (Dart) · about 5 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise: predict which client snippets compile

Five snippets below try to subtype a class from another library. Predict which compile and which fail, then compare with the analysed result:

Five snippets analysed Python · predict_session.py
#!/usr/bin/env python3
"""Session driver to analyse five snippets that try to subtype modified classes.

Each line of the script is one command typed into one shell, so the snippet
files are created with write() before the session runs rather than with a shell
heredoc (which the line-by-line driver cannot keep together)."""
import session

session.files("modifiers")

session.write("snippet1.dart", """import 'modifiers/payments.dart';

// Money is final: extending it outside its library is an error.
class ExtendMoney extends Money {
  ExtendMoney(super.paise);
}

void main() {}
""")

session.write("snippet2.dart", """import 'modifiers/payments.dart';

// Gateway is an interface class: implementing it outside its library is allowed.
class MockGateway implements Gateway {
  @override
  bool process(Money amount) => true;
}

void main() {}
""")

session.write("snippet3.dart", """import 'modifiers/payments.dart';

// Ledger is a base class: implementing it outside its library is an error,
// even when the implementer is itself base.
base class FakeLedger implements Ledger {
  @override
  void record(Money amount) {}
  @override
  Money get total => Money(0);
}

void main() {}
""")

session.write("snippet4.dart", """import 'modifiers/payments.dart';

// Ledger is a base class: extending it is allowed when the subtype is base.
base class AppLedger extends Ledger {
  @override
  void record(Money amount) {
    super.record(amount);
  }
}

void main() {}
""")

session.write("snippet5.dart", """import 'modifiers/payments.dart';

// PaymentResult is sealed: extending it outside its library is an error.
class CustomResult extends PaymentResult {}

void main() {}
""")

session.run("""
# Snippet 1: extend a final class -> error
! dart analyze snippet1.dart
# Snippet 2: implement an interface class -> compiles
dart analyze snippet2.dart
# Snippet 3: implement a base class from another library -> error
! dart analyze snippet3.dart
# Snippet 4: extend a base class with a base subtype -> compiles
dart analyze snippet4.dart
# Snippet 5: extend a sealed class -> error
! dart analyze snippet5.dart
""")

Output

# Snippet 1: extend a final class -> error
$  dart analyze snippet1.dart
Analyzing snippet1.dart...

  error - snippet1.dart:4:27 - The class 'Money' can't be extended outside of its library because it's a final class. - invalid_use_of_type_outside_library

1 issue found.
[exit status 3]
# Snippet 2: implement an interface class -> compiles
$ dart analyze snippet2.dart
Analyzing snippet2.dart...
No issues found!
# Snippet 3: implement a base class from another library -> error
$  dart analyze snippet3.dart
Analyzing snippet3.dart...

  error - snippet3.dart:5:34 - The class 'Ledger' can't be implemented outside of its library because it's a base class. - invalid_use_of_type_outside_library

1 issue found.
[exit status 3]
# Snippet 4: extend a base class with a base subtype -> compiles
$ dart analyze snippet4.dart
Analyzing snippet4.dart...
No issues found!
# Snippet 5: extend a sealed class -> error
$  dart analyze snippet5.dart
Analyzing snippet5.dart...

  error - snippet5.dart:4:28 - The class 'PaymentResult' can't be extended, implemented, or mixed in outside of its library because it's a sealed class. - invalid_use_of_type_outside_library

1 issue found.
[exit status 3]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 predict_session.py

Practice that adapts to you

Practise this lesson

4 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 4 A library has final class Money. Another library writes class Token implements Money. What happens?

    Choose one answer.

    Show the answer: Question 1 of 4

    Answer: Compile error: Money cannot be implemented

    A final class closes the type hierarchy entirely. No code outside its library can extend or implement it, so every instance of that type is guaranteed to be a direct instance or a subtype from the same library.

  2. Question 2 of 4 You want tests to fake a class but prevent production code from inheriting its implementation. Which modifier?

    Choose one answer.

    Show the answer: Question 2 of 4

    Answer: interface class

    Interface class is designed for this pattern: users can implement the interface to provide their own code, but they cannot extend the class and inherit its private implementation details.

  3. Question 3 of 4 Where can the direct subtypes of a sealed class be declared?

    Choose one answer.

    Show the answer: Question 3 of 4

    Answer: Only in the library that declares the sealed class

    payments.dart declares Success and Failure next to the sealed PaymentResult; the same subtype in another library is refused because the class is sealed.

  4. Question 4 of 4 The library that declares interface class Gateway also declares class Audited with Gateway {}. What happens?

    Choose one answer.

    Show the answer: Question 4 of 4

    Answer: A compile error: only a mixin or a mixin class may follow with, inside the library too

    Inside its library an interface class may be extended (LoggingGateway compiled), but with needs a mixin declaration or a mixin class wherever it is written.

Interview questions

Warm-up (fresher to 2–5 years): when would you pick final class, and when sealed class, for a new Dart type?

Both prevent extending and implementing outside the library, but sealed also prevents constructing and makes the class implicitly abstract. The key difference is intent: final closes the hierarchy when you want users to use instances but never subtype them, while sealed closes it when you want the compiler to know every subtype for exhaustive switches. A final class Money can be constructed; a sealed class PaymentResult cannot, and its subtypes appear in the same library so a switch over them is exhaustive.

Two longer questions from this module’s bank cover adding modifiers to a published library and the rules that spread to subtypes:

  1. Mid to senior

    A payments library published version 1.0.0 with an open Gateway class. Some users extend it, others implement it. You want to add interface to Gateway in the next release. Plan the migration so that users have time to adapt, and explain which code breaks and when.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to staff

    Your library exports base class Ledger. A user's code compiles today with class AppLedger extends Ledger. In a new release you add a private method _auditEntry() that record() calls. Does the user's code still compile, and if not, what do they see?

    Model answer, Pro. First step and follow-ups

  3. Senior to staff

    You maintain a Dart package with a class that users extend, implement, and mix in. You want to restrict it to implementing only. Describe the deprecation and release sequence, naming the exact annotation constructors available since Dart 3.10 and what each one warns about.

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Why does adding a method to an exported class break the packages that implement it?

    Every implementer must provide every member, so the new method is missing in each of them. Since Dart 3.0, class modifiers let a library say what other libraries may do with its class, and the compiler enforces it.

  2. What do base, interface, final and sealed allow another library to do?

    base allows extending but not implementing, interface implementing but not extending, final neither, and sealed neither while it enables exhaustive switches.

  3. What may another library do with a class that has no modifier?

    Construct it, extend it and implement it. It may not mix it in unless the class is a mixin class.

  4. Which classes can another library not construct?

    sealed and abstract classes, and a mixin. Classes marked base, interface or final can still be constructed.

  5. Inside its own library, may code extend a sealed or final class?

    Yes: the limits apply only to other libraries, which is how a sealed class's subtypes sit next to it. Two rules still hold: a subtype of a base or final class must be base, final or sealed, and only a mixin or mixin class may follow with.

  6. For a new type, when final class and when sealed class?

    Both stop other libraries from extending and implementing it. A final class can be constructed and ends the hierarchy; a sealed class is implicitly abstract and its subtypes in the same library make switches over it exhaustive.

Key takeaways

  • Class modifiers control what other libraries may do with a class: base allows extending, interface allows implementing, final allows neither, and sealed closes the hierarchy for exhaustive switching.
  • Inside its own library any class can be extended and implemented, but a subtype of a base or final class must still be base, final or sealed, and only a mixin or mixin class can follow with.
  • A subtype of a base class must itself be base, final, or sealed, which is why base transitivity spreads down the hierarchy.
  • Adding a modifier to a published class is a breaking change: A change that makes code that compiled before stop compiling or behave differently, requiring a new major version under semantic versioning.: client code that compiled against the old version stops compiling against the new one.

Terms in this lesson

base class
A class marked base that can be extended but not implemented outside its library; subtypes must also be base, final, or sealed.
breaking change
A change that makes code that compiled before stop compiling or behave differently, requiring a new major version under semantic versioning.
class modifier
A keyword before a class declaration that controls what other libraries may do with it: base, interface, final, sealed, abstract or mixin.
final class
A class marked final that cannot be extended or implemented outside its library, closing the type hierarchy entirely.
interface class
A class marked interface that can be implemented but not extended outside its library, useful when you want users to provide their own implementations.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.