Dart Fundamentals: The Language Behind Flutter · Classes, Modern Dart 3 Features and Async

Dart 3: Records, Patterns and Sealed Classes

Return several values with records, destructure data with patterns, write exhaustive switch expressions, and model states with sealed classes so the compiler checks you handled every case.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

A network request result modelled as a sealed class (Loading, Success, Failure), rendered with an exhaustive switch expression, plus functions that return records and destructure JSON-like maps with patterns.

The idea

Dart 3 added features that make data-heavy code short and safe:

  • Records: lightweight, immutable groups of values: (int, String) or with names ({double lat, double lng}). Great for returning several values: (min, max) = range(values);.
  • Patterns: destructure records, lists, maps and objects: var [first, ...rest] = list;, if (json case {'name': String name, 'age': int age}) ....
  • Switch expressions: final label = switch (code) { 200 => 'ok', 404 => 'missing', _ => 'other' };.
  • Sealed classes: a closed family of subtypes, all in one library. A switch over a sealed type must handle every subtype, or the code doesn't compile, just like Rust's enums.

The code

Sealed states, switch expressions, records and patterns
sealed class FetchState {}

class Loading extends FetchState {}

class Success extends FetchState {
  final List<String> items;
  Success(this.items);
}

class Failure extends FetchState {
  final int status;
  final String message;
  Failure(this.status, this.message);
}

String render(FetchState state) => switch (state) {
      Loading() => 'Loading…',
      Success(items: []) => 'Nothing here yet',
      Success(:final items) => 'Showing ${items.length}: ${items.join(', ')}',
      Failure(status: 401) => 'Please sign in again',
      Failure(:final status, :final message) => 'Error $status: $message',
    }; // adding a new subclass makes this a compile error until it's handled

(double, double, double) stats(List<double> values) {
  final sorted = [...values]..sort();
  final avg = values.reduce((a, b) => a + b) / values.length;
  return (sorted.first, sorted.last, avg);
}

String describeUser(Map<String, Object?> json) {
  if (json case {'name': String name, 'age': int age} when age >= 18) {
    return '$name (adult)';
  }
  if (json case {'name': String name}) {
    return '$name (age unknown or minor)';
  }
  return 'invalid user';
}

void main() {
  final states = [Loading(), Success([]), Success(['milk', 'eggs']), Failure(401, 'expired'), Failure(503, 'try later')];
  for (final s in states) {
    print(render(s));
  }

  final (low, high, average) = stats([21.5, 19.0, 24.0, 22.5]); // destructure a record
  print('low $low, high $high, average ${average.toStringAsFixed(2)}');

  final location = (lat: 38.72, lng: -9.14); // a record with named fields
  print('Lisbon at ${location.lat}, ${location.lng}');

  final [first, second, ...rest] = ['gold', 'silver', 'bronze', 'fourth', 'fifth'];
  print('$first, $second, and ${rest.length} more');

  for (final json in [{'name': 'Ana', 'age': 34}, {'name': 'Leo', 'age': 12}, {'title': 'oops'}]) {
    print(describeUser(json));
  }
}

Output

Loading…
Nothing here yet
Showing 2: milk, eggs
Please sign in again
Error 503: try later
low 19.0, high 24.0, average 21.75
Lisbon at 38.72, -9.14
gold, silver, and 3 more
Ana (adult)
Leo (age unknown or minor)
invalid user

Run this on your computer — see “Get Started” for setup.

Common mistakes

  • Using _ in switches over sealed types. It hides new subtypes; list them so the compiler can help.
  • Records for long-lived domain models. Records are great for small groupings; use classes when you need methods and names that document intent.
  • Forgetting that records compare by value. (1, 2) == (1, 2) is true, unlike plain classes.
  • Over-complex patterns. Split very nested patterns into helper functions for readability.

Key points

  • Records (a, b) / (x: .., y: ..) group values and compare by value.
  • Patterns destructure lists, maps, records and objects (case, when guards).
  • Switch expressions return values; sealed classes make them exhaustive.
  • Sealed classes are ideal for UI states: loading, success, failure.

Further reading: Records, Patterns, Sealed classes.

Practice questions

What do sealed classes give you in Dart 3?

Answer

A closed set of subtypes defined in the same library. The compiler knows every subtype, so a switch over the sealed type must handle all of them; adding a subtype produces compile errors wherever it isn't handled.

What are Dart records?

Answer

Anonymous, immutable aggregates of values with positional and/or named fields, e.g. (int, String) or ({double lat, double lng}). They're typed, compare by value, and are handy for returning multiple values.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?