What are sealed classes in Dart?

Medium PriorityAsked in ~45% of Flutter interviews (rising fast)

3 min read

Dart 3 / OOP

ModifierSubclassableSubtypes must be in same library?Implicitly abstract?
(none)✅ Yes
abstract✅ Yes
sealed✅ YesYes
final❌ Non/aoptional
base✅ subclasses must be base/final/sealedoptional

Sealed = "I know all my subtypes at compile time." The compiler uses that knowledge to make switch exhaustive without a default branch.


Code in action

sealed class Result<T> {}
class Success<T> extends Result<T> { final T value;        Success(this.value); }
class Failure<T> extends Result<T> { final String error;   Failure(this.error); }
class Loading<T> extends Result<T> {}

// Exhaustive — no `default` needed, compiler covers all cases
String describe(Result<String> r) => switch (r) {
  Success(:final value)  => 'Got $value',
  Failure(:final error)  => 'Oops: $error',
  Loading()              => 'Loading…',
};

If you add class Cached<T> extends Result<T> and forget to update describe, the build fails — the compiler points you at every switch. That's the whole point.


Killer use case: app state

sealed class AuthState {}
class AuthIdle          extends AuthState {}
class AuthLoading       extends AuthState {}
class Authenticated     extends AuthState { final User user;   Authenticated(this.user); }
class AuthFailed        extends AuthState { final String msg;  AuthFailed(this.msg); }

Widget build(AuthState s) => switch (s) {
  AuthIdle()                  => const LoginScreen(),
  AuthLoading()               => const Spinner(),
  Authenticated(:final user)  => HomeScreen(user: user),
  AuthFailed(:final msg)      => ErrorBanner(msg),
};

Same shape works beautifully for ApiResponse<T>, NavigationEvent, FormFieldState, etc.


Sealed vs abstract vs enum

You need…Use
Closed set of states, each carrying different datasealed class
Closed set of states with no data (statuses, flags)enum
Open hierarchy (third parties can subclass)abstract class
Lock down the class entirely (no subclassing anywhere)final class

Common mistakes to avoid

// ❌ Sealed class with subtypes in a different file
// lib/auth/state.dart
sealed class AuthState {}

// lib/auth/login.dart
class LoginSuccess extends AuthState {}     // ❌ must be in the same library

// ✅ Use part files or keep them in the same file

// ❌ Using `default:` and missing the exhaustiveness benefit
switch (state) {
  case AuthIdle(): ...
  default: throw 'unreachable';            // hides missing cases from the compiler
}
// ✅ Drop the default — the compiler will warn if you miss a case

// ❌ Treating it like a normal abstract class with public constructors
sealed class Result {
  Result();                                 // ok, but...
}
class External {}                           // anywhere outside the lib
// extends Result not possible — but that's the feature, not a bug

// ❌ Sealing when you don't switch on it
sealed class Animal {}                      // adds friction with no benefit
// ✅ Only seal when exhaustive switch / pattern matching pays off

// ❌ Forgetting to handle a destructure with `:final` for nullable fields
case Authenticated(:final user) when user.isAdmin => ...;
// fine, but the type system still tracks user as non-null because Authenticated declared it

Interview follow-ups

  1. What problem do sealed classes solve over plain abstract classes? Exhaustiveness. A plain abstract class can be extended anywhere, so the compiler can't know all subtypes — switch always needs a default. sealed closes the set, enabling compile-time guarantees that every case is handled.

  2. Why must sealed subclasses live in the same library? So the compiler can prove the set is closed. If you could extend a sealed class from another package, the compiler would have no way to enumerate subtypes — and the exhaustiveness guarantee would silently break for downstream consumers.

  3. When should you use a sealed class vs an enum? enum for finite states with no payload (Status.idle, Status.done). sealed class when each case carries different data (a success has a value, a failure has an error). Dart 3 enhanced enums blur the line, but rule of thumb: payload → sealed, label → enum.

  4. What's the difference between sealed, final, and base class modifiers? sealed = abstract + closed subtype set. final = nobody can extend or implement it at all. base = can be extended but only by classes that are also base/final/sealed (prevents accidental implements-only subtypes). They're orthogonal tools for class-level access control.

How helpful was this content?

Please sign in to rate this article.