Dart: Dart 3 New Features

Last updated: 2026-08-26

The three major features of Dart 3 — upgrade your code from "it works" to "elegant."

1. What You Will Learn


2. A Developer's Real Story

(1) Pain Point: Deep Type Hierarchy and Verbose if-else

Bob's DataPipeline has 3 data sources (API/File/Database), each with different configurations and connection methods. He implemented them using inheritance, but the hierarchy was deep and the code was verbose. When parsing API responses, a large number of if-else checks were needed for status codes and response structures, with 200 lines of conditional code written for just 5 response types.

(2) Dart 3 New Features Solution

Sealed Classes restrict data source types, Pattern Matching elegantly handles various responses, and Records provide lightweight multiple return values.

DART
// Sealed class: exhaustive data source types
sealed class DataSource {}
class ApiSource extends DataSource { final String endpoint; ApiSource(this.endpoint); }
class FileSource extends DataSource { final String path; FileSource(this.path); }

// Pattern matching on sealed class - compiler guarantees all cases handled
String describe(DataSource source) => switch (source) {
  ApiSource(:final endpoint) => 'API: $endpoint',
  FileSource(:final path) => 'File: $path',
};

// Records: return multiple values
(String, double) parseResponse(String raw) => ('ORD-001', 1500.0);
TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

(3) Benefits


3. Records

(1) Record Basics

▶ Example

TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

: Creating and Using Records

DART
void main() {
  // Positional record
  var order = ('ORD-001', 1500.0, 'completed');
  print(order.$1);  // ORD-001
  print(order.$2);  // 1500.0
  print(order.$3);  // completed

  // Named record (preferred for readability)
  var order2 = (id: 'ORD-002', amount: 3200.0, status: 'pending');
  print(order2.id);      // ORD-002
  print(order2.amount);  // 3200.0
  print(order2.status);  // pending

  // Mixed positional and named
  var mixed = ('ORD-003', 890.0, category: 'Electronics');
  print(mixed.$1);       // ORD-003
  print(mixed.category); // Electronics
}
TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

▶ Example

TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

: Records as Function Return Values

DART
// Multiple return values with records
({String id, double amount, double tax}) calculateOrder(double amount, double taxRate) {
  return (
    id: 'ORD-${DateTime.now().millisecondsSinceEpoch}',
    amount: amount,
    tax: amount * taxRate,
  );
}

// Positional record for quick grouping
(String, double) parseAmount(String input) {
  final parts = input.split(':');
  return (parts[0], double.parse(parts[1]));
}

void main() {
  final order = calculateOrder(1500.0, 0.08);
  print('ID: ${order.id}, Amount: \$${order.amount}, Tax: \$${order.tax}');

  final (label, value) = parseAmount('Revenue:52500.75');
  print('$label: \$$value USD');
}
TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.
Record Feature Syntax Description
Positional fields .$1, .$2 Access by position
Named fields .name Access by name
Type annotation (String, int) or ({String name, int count}) Explicit type
Equality Value equality Equal if field values are the same

4. Pattern Matching

(1) Pattern Matching Types

▶ Example

TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

: Variable Patterns and Destructuring

DART
void main() {
  // Variable pattern - extract values
  var (id, amount, status) = ('ORD-001', 1500.0, 'completed');
  print('ID: $id, Amount: $amount, Status: $status');

  // Record destructuring with named fields
  var (:id, :amount, :status) = (id: 'ORD-002', amount: 3200.0, status: 'pending');
  print('ID: $id, Amount: $amount, Status: $status');

  // List destructuring
  var [first, second, ...rest] = [1, 2, 3, 4, 5];
  print('First: $first, Second: $second, Rest: $rest');

  // Map destructuring
  var {'id': orderId, 'amount': orderAmount} = {'id': 'ORD-003', 'amount': 890.0};
  print('Order: $orderId, Amount: $orderAmount');
}
TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

▶ Example

TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

: if-case Pattern

DART
void main() {
  Object value = '1,500.00 USD';

  // if-case pattern matching
  if (value case String s when s.contains('USD')) {
    print('USD value: $s');
  }

  // Pattern matching on record
  var response = (statusCode: 200, body: '{"orders": 1500}');
  if (response case (statusCode: 200, :var body)) {
    print('Success response: $body');
  }

  // Pattern matching on list
  var orders = ['ORD-001', 'ORD-002', 'ORD-003'];
  if (orders case [var first, var second, ...]) {
    print('First two: $first, $second');
  }
}
TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

▶ Example

TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

: switch Pattern

DART
String classifyOrder(Object value) => switch (value) {
  // Type pattern
  int i when i > 1000 => 'High value integer: $i',
  int i => 'Low value integer: $i',
  double d when d >= 1000 => 'Premium: \$${d.toStringAsFixed(2)}',
  double d => 'Standard: \$${d.toStringAsFixed(2)}',
  String s => 'String: $s',
  List l when l.length > 100 => 'Large batch: ${l.length} items',
  List l => 'Small batch: ${l.length} items',
  _ => 'Unknown type',
};

// Pattern matching with records
String describeResponse((int, String) response) => switch (response) {
  (200, var body) => 'OK: $body',
  (404, _) => 'Not Found',
  (500, var msg) => 'Server Error: $msg',
  (>= 400, var msg) => 'Client Error: $msg',
  _ => 'Unknown response',
};

void main() {
  print(classifyOrder(1500));          // High value integer: 1500
  print(classifyOrder(890.0));         // Standard: $890.00
  print(classifyOrder([1, 2, 3]));     // Small batch: 3 items

  print(describeResponse((200, 'OK')));      // OK: OK
  print(describeResponse((404, 'Missing')));  // Not Found
  print(describeResponse((500, 'Crash')));    // Server Error: Crash
}
TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.
Pattern Type Syntax Usage
Variable pattern var x Extract values

5. Sealed Classes

(1) Sealed Class Hierarchy

100%
classDiagram
  class DataSource {
    <<sealed>>
  }
  class ApiSource {
    +String endpoint
    +Map headers
  }
  class FileSource {
    +String path
    +Encoding encoding
  }
  class DatabaseSource {
    +String connectionString
  }
  DataSource <|-- ApiSource
  DataSource <|-- FileSource
  DataSource <|-- DatabaseSource
  note for DataSource "exhaustive switch guaranteed"
TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

▶ Example

TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

: Sealed Class Definition and Exhaustive Matching

DART
sealed class DataSource {
  const DataSource();
}

class ApiSource extends DataSource {
  final String endpoint;
  final Map<String, String> headers;

  const ApiSource(this.endpoint, {this.headers = const {}});
}

class FileSource extends DataSource {
  final String path;

  const FileSource(this.path);
}

class DatabaseSource extends DataSource {
  final String connectionString;

  const DatabaseSource(this.connectionString);
}

// Exhaustive switch - compiler checks all subtypes
String describeSource(DataSource source) => switch (source) {
  ApiSource(:final endpoint, :final headers) =>
      'API: $endpoint (${headers.length} headers)',
  FileSource(:final path) =>
      'File: $path',
  DatabaseSource(:final connectionString) =>
      'Database: $connectionString',
};

// If you add a new subtype and forget to update switch,
// the compiler will report an error!
void main() {
  final sources = ``<DataSource>``[
    ApiSource('https://api.example.com/orders', headers: {'Authorization': 'Bearer token'}),
    FileSource('/data/orders.csv'),
    DatabaseSource('postgresql://localhost:5432/orders'),
  ];

  for (final source in sources) {
    print(describeSource(source));
  }
}
TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.
Sealed Class Feature Description
Subclass restriction Must be defined in the same library
Exhaustive check switch must cover all subtypes
Compile-time guarantee Omitted switch will cause compile error when new subclass is added
Cannot be instantiated Is implicitly abstract

6. The Trio in Action

▶ Example

TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

: Sealed + Pattern + Records Combination

DART
// Sealed class for API results
sealed class ApiResult``<T>`` {
  const ApiResult();
}

class Success``<T>`` extends ApiResult``<T>`` {
  final T data;
  final (int, String) metadata;  // Record: (statusCode, message)

  const Success(this.data, this.metadata);
}

class ApiError``<T>`` extends ApiResult``<T>`` {
  final String message;
  final int? statusCode;

  const ApiError(this.message, {this.statusCode});
}

class NetworkError``<T>`` extends ApiResult``<T>`` {
  final String reason;

  const NetworkError(this.reason);
}

// Pattern matching with destructuring
String handleResult(ApiResult<List``<String>``> result) => switch (result) {
  Success(:final data, metadata: (200, final msg)) =>
      'OK ($msg): ${data.length} items loaded',
  Success(:final data, metadata: (final code, _)) =>
      'Loaded with status $code: ${data.length} items',
  ApiError(:final message, statusCode: final code?) =>
      'API Error [$code]: $message',
  ApiError(:final message) =>
      'API Error: $message',
  NetworkError(:final reason) =>
      'Network Error: $reason',
};

void main() {
  final results = <ApiResult<List``<String>``>>[
    Success(['ORD-001', 'ORD-002'], (200, 'OK')),
    Success(['ORD-003'], (206, 'Partial Content')),
    ApiError('Rate limit exceeded', statusCode: 429),
    ApiError('Unknown error'),
    NetworkError('Connection timeout'),
  ];

  for (final result in results) {
    print(handleResult(result));
  }
}
TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

7. Bob's Scenario: DataPipeline Data Sources and Result Parsing

▶ Example

TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

: Complete Data Source Abstraction

DART
sealed class DataSource {
  const DataSource();

  String get displayName;
}

class ApiSource extends DataSource {
  final String endpoint;
  final Duration timeout;

  const ApiSource(this.endpoint, {this.timeout = const Duration(seconds: 10)});

  @override
  String get displayName => 'API ($endpoint)';
}

class FileSource extends DataSource {
  final String path;
  final String format;

  const FileSource(this.path, {this.format = 'csv'});

  @override
  String get displayName => 'File ($path, $format)';
}

class DatabaseSource extends DataSource {
  final String connectionString;
  final String query;

  const DatabaseSource(this.connectionString, {this.query = 'SELECT * FROM orders'});

  @override
  String get displayName => 'Database ($connectionString)';
}

// Processing result with Records
typedef ProcessingResult = ({int processed, int skipped, double revenue, Duration time});

ProcessingResult processDataSource(DataSource source) => switch (source) {
  ApiSource(:final endpoint, :final timeout) => (
    processed: 50000,
    skipped: 120,
    revenue: 525000.0,
    time: timeout,
  ),
  FileSource(:final path, :final format) => (
    processed: 100000,
    skipped: 350,
    revenue: 1200000.0,
    time: Duration(seconds: 5),
  ),
  DatabaseSource(:final connectionString, :final query) => (
    processed: 1200000,
    skipped: 800,
    revenue: 15000000.0,
    time: Duration(seconds: 15),
  ),
};

void main() {
  final sources = ``<DataSource>``[
    ApiSource('https://api.example.com/orders'),
    FileSource('/data/orders.csv'),
    DatabaseSource('postgresql://localhost:5432/orders'),
  ];

  print('=== DataPipeline Source Analysis ===');
  for (final source in sources) {
    final (:processed, :skipped, :revenue, :time) = processDataSource(source);
    print('\n${source.displayName}:');
    print('  Processed: ${processed} orders');
    print('  Skipped:   $skipped records');
    print('  Revenue:   \$${revenue.toStringAsFixed(2)} USD');
    print('  Time:      ${time.inSeconds}s');
  }
}
TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

8. Complete Example: DataPipeline Response Processing System

DART
// ============================================
// DataPipeline Response Processing System
// Sealed classes + Pattern matching + Records
// ============================================

// Sealed class for different response types
sealed class ApiResponse {
  const ApiResponse();
}

class SuccessResponse extends ApiResponse {
  final int statusCode;
  final Map<String, dynamic> data;

  const SuccessResponse(this.statusCode, this.data);
}

class ErrorResponse extends ApiResponse {
  final int statusCode;
  final String message;
  final String? details;

  const ErrorResponse(this.statusCode, this.message, {this.details});
}

class TimeoutResponse extends ApiResponse {
  final Duration timeout;
  final String endpoint;

  const TimeoutResponse(this.timeout, this.endpoint);
}

class RedirectResponse extends ApiResponse {
  final String newEndpoint;
  final int statusCode;

  const RedirectResponse(this.newEndpoint, this.statusCode);
}

// Processing outcome as a Record
typedef Outcome = ({bool success, String message, double? revenue});

// Pattern matching on all response types - exhaustive!
Outcome handleResponse(ApiResponse response) => switch (response) {
  SuccessResponse(statusCode: 200, :final data) when data.containsKey('orders') => (
    success: true,
    message: 'Loaded ${data['orders']} orders',
    revenue: (data['revenue'] as num?)?.toDouble(),
  ),
  SuccessResponse(statusCode: 200, :final data) => (
    success: true,
    message: 'OK but no orders field: ${data.keys}',
    revenue: null,
  ),
  SuccessResponse(statusCode: 206, :final data) => (
    success: true,
    message: 'Partial data: ${data.length} fields',
    revenue: null,
  ),
  SuccessResponse(statusCode: final code) => (
    success: true,
    message: 'Unexpected success code: $code',
    revenue: null,
  ),
  ErrorResponse(statusCode: 429, :final message) => (
    success: false,
    message: 'Rate limited: $message',
    revenue: null,
  ),
  ErrorResponse(statusCode: final code, :final message, :final details?) => (
    success: false,
    message: 'Error [$code]: $message - $details',
    revenue: null,
  ),
  ErrorResponse(statusCode: final code, :final message) => (
    success: false,
    message: 'Error [$code]: $message',
    revenue: null,
  ),
  TimeoutResponse(:final timeout, :final endpoint) => (
    success: false,
    message: 'Timeout after ${timeout.inSeconds}s on $endpoint',
    revenue: null,
  ),
  RedirectResponse(:final newEndpoint, :final statusCode) => (
    success: false,
    message: 'Redirect ($statusCode) to $newEndpoint',
    revenue: null,
  ),
};

void main() {
  final responses = ``<ApiResponse>``[
    SuccessResponse(200, {'orders': 50000, 'revenue': 525000.0}),
    SuccessResponse(200, {'products': 3000}),
    SuccessResponse(206, {'partial': true}),
    ErrorResponse(429, 'Rate limit exceeded'),
    ErrorResponse(500, 'Internal server error', details: 'Database connection lost'),
    ErrorResponse(404, 'Not found'),
    TimeoutResponse(const Duration(seconds: 30), '/api/v1/orders'),
    RedirectResponse('/api/v2/orders', 301),
  ];

  print('=== DataPipeline Response Handler ===\n');
  for (final response in responses) {
    final (:success, :message, :revenue) = handleResponse(response);
    final status = success ? 'OK' : 'FAIL';
    final rev = revenue != null ? ' (\$${revenue.toStringAsFixed(2)} USD)' : '';
    print('[$status] $message$rev');
  }
}
TEXT 📖 Display only
> **Output:** Run in local DartPad or using `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly depending on the SDK version.

Output:

TEXT 📖 Display only
=== DataPipeline Response Handler ===

[OK] Loaded 50000 orders ($525000.00 USD)
[OK] OK but no orders field: (products)
[OK] Partial data: 1 fields
[FAIL] Rate limited: Rate limit exceeded
[FAIL] Error [500]: Internal server error - Database connection lost
[FAIL] Error [404]: Not found
[FAIL] Timeout after 30s on /api/v1/orders
[FAIL] Redirect (301) to /api/v2/orders

❓ FAQ

Q: What's the difference between Records and Classes? A: Records are anonymous, value-based lightweight data structures; Classes have names, are reference-based, and can have methods. Records are suitable for simple data passing, while Classes are for complex business logic.

Q: What's the difference between a Sealed class and an abstract class? A: Sealed classes restrict subclasses to the same library, allowing the compiler to perform exhaustive checks; abstract classes can have subclasses defined anywhere, preventing exhaustiveness. Use sealed when you need exhaustive guarantees.

Q: Where can Pattern matching be used? A: Switch expressions, switch statements, if-case, variable declarations, for-in loops. In Dart 3, patterns are everywhere.

Q: What is the performance of Records? A: Records compile to ordinary objects, with performance comparable to small classes. There is no extra overhead, but they are also not faster than classes. Choose based on readability.

Q: Can a sealed class have a constructor? A: It can have factory constructors (which return subclasses), but the class itself cannot be instantiated (it's implicitly abstract). Constructors are mainly for sharing initialization logic among subclasses.

Q: Is there a difference between _ and default in a switch expression? A: Functionally, they are the same – both are catch-all matches. Dart 3 recommends using _ (wildcard pattern) for conciseness. default is the legacy syntax retained for compatibility.

Q: Can Records be used as keys for a Map? A: Yes. Records are based on value equality and automatically implement == and hashCode. (1, 2) == (1, 2) is true.


📖 Summary


📝 Exercises

  1. Basic (⭐): Define two Records: a positional record (String, double) representing order ID and amount, and a named record ({String id, double amount, String status}). Create instances and destructure/print them.
  2. Intermediate (⭐⭐): Use a Sealed Class to define 3 payment methods (CreditCard/PayPal/BankTransfer), each with different fields. Implement a describePayment() method using a switch expression, ensuring exhaustiveness.
  3. Challenge (⭐⭐⭐): Design a complete API response processing system: Use a Sealed Class to define response types (Success/ValidationError/ServerError/Timeout), each carrying different Record data. Implement a response handler using Pattern Matching, incorporating relational patterns and guard conditions.

← Previous Lesson | Next Lesson →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏