Irbisa · Cheatsheet

Dart 3 Patterns & Records

Records, destructuring, switch expressions, if-case guards, sealed types, exhaustiveness

Middle Developer · 19 questions

  1. 01

    What is a record in Dart 3, and how do positional and named fields differ?

    A record is an anonymous, immutable, fixed-size group of values, like (200, 'ok') or (name: 'Ada', age: 36).

    // The type is the shape: nothing declares (int, String) anywhere.(int, String) parse(String raw) => (raw.length, raw.toUpperCase()); typedef User = ({String name, int age}); // a typedef only names a shape void main() {…
  2. 02

    How do you destructure a record into variables, and where else can a destructuring pattern appear?

    You write a pattern with the record's shape on the left: final (code, body) = fetch(); for positional fields, final (:name, :age) = user; for named ones.

    typedef User = ({String name, int age}); (int, String) fetch() => (200, 'ok'); void main() {  final (code, body) = fetch(); // positional: bound by position…
  3. 03

    What is the difference between a switch statement and a switch expression in Dart 3?

    A switch statement runs code for the matching case; a switch expression evaluates to a value, so you can assign it, return it or pass it as an argument.

    enum Status { active, blocked, deleted } // Expression: produces a value; arms are `pattern => value`.String label(Status s) => switch (s) {      Status.active => 'Active',      Status.blocked || Status.deleted => 'Unavailable',…
  4. 04

    What does this code print, and why does the last lookup miss even though the key looks identical?

    void main() {  print((1, 'a') == (1, 'a'));  print((x: 1, y: 2) == (y: 2, x: 1));  print((1, 2) == (x: 1, y: 2));   final cache = <(int, String), String>{(1, 'en'): 'Hello'};  print(cache[(1, 'en')]);   final byTags = <(List<String>, int), String>{(['a'], 1): 'x'};  print(byTags[(['a'], 1)]);}

    It prints true, true, false, Hello, null.

  5. 05

    When would you return a record instead of a class or a Map, and when is a record the wrong choice?

    Use a record for a small, short-lived group of values, typically two or three results of a private function: it is fully typed like a class and needs no declaration.

    // wrong: every caller re-guesses the keys and castsMap<String, dynamic> loadOld() => {'items': <String>[], 'total': 0}; // right: the shape is part of the signature(List<String> items, int total) loadPage() => (['a', 'b'], 42);…
  6. 06

    What is pattern matching in Dart 3, and which older idioms does it replace?

    A pattern describes the shape a value should have; matching checks the value against it and binds the parts you name to new variables in one step.

    // before: is-checks, casts and nested conditionsString describeOld(Object json) {  if (json is Map<String, dynamic>) {    final user = json['user'];    if (user is Map<String, dynamic> && user['name'] is String) {      return 'user ${user['name'] as String}';…
  7. 07

    What is a sealed class, how does it enable an exhaustive switch, and why is it better than an abstract class for modelling state?

    A sealed class is an implicitly abstract class whose direct subtypes must all be declared in the same library, so the compiler knows the complete list.

    sealed class AuthState {} final class SignedOut extends AuthState {} final class SigningIn extends AuthState {}…
  8. 08

    What does if (value case pattern when condition) do, and what does it replace?

    It matches value against a pattern, binds the variables the pattern names, and then checks an extra boolean guard, all in one condition.

    class Session {  String? token;} // before: two reads of a mutable field and a `!`void sendOld(Session s) {…
  9. 09

    What does an object pattern like Cart(total: > 100) check, and what code does it run?

    It first tests value is Cart, then calls the total getter and matches the result against > 100.

    class Item {  const Item(this.price);  final int price;} class Cart {…
  10. 10

    How does a list pattern like [first, ..., last] match, and what does a named rest element ...final rest cost?

    It checks that the value is a List, checks its length (exactly the element count without a rest element, at least that count with one) and reads elements by index from both ends.

    String summary(List<String> names) => switch (names) {      [] => 'nobody',      [final only] => only,      [final a, final b] => '$a and $b',      [final first, ..., final last] => '$first … $last', // nothing copied    };…
  11. 11

    How does a map pattern match decoded JSON, and what happens with extra keys or a missing key?

    A map pattern checks that the value is a Map, then looks up each key you listed and matches its value against a subpattern.

    String greet(Map<String, Object?> json) {  if (json      case {'type': 'user', 'name': final String name, 'age': final int age}) {    return '$name, $age'; // extra keys in json are ignored  }  if (json case {'type': 'user', 'name': final String name}) {…
  12. 12

    What are relational and logical patterns, and when do you need a when guard instead?

    Relational patterns compare the value with a constant using ==, !=, <, <=, > or >=; logical patterns combine patterns with && and ||.

    const adult = 18;const retirement = 65; String bracket(int age) => switch (age) {      < 0 => 'invalid',      < adult => 'minor',…
  13. 13

    What does this program print, and why does the order of the switch arms matter?

    String describe(Object o) => switch (o) {      int n when n > 10 => 'big int',      int() => 'int',      num n => 'num $n',      String(isEmpty: true) => 'empty string',      String s => 'string $s',      _ => 'other',    }; void main() {  for (final v in [42, 7, 2.5, '', 'hi', true]) {    print(describe(v));  }}

    It prints big int, int, num 2.5, empty string, string hi, other.

  14. 14

    Why does this switch fail to compile even though every subtype of Result appears in it?

    sealed class Result {} final class Ok extends Result {  Ok(this.value);  final int value;} final class Err extends Result {  Err(this.message);  final String message;} String show(Result r) => switch (r) {      Ok(:final value) when value > 0 => 'positive $value',      Err(:final message) => 'error: $message',    };

    The only Ok arm has a when guard, and guarded arms do not count towards exhaustiveness.

  15. 15

    Find the bug: the loading label never appears; users see "Something went wrong" while data is still loading.

    sealed class LoadState {}final class Loading extends LoadState {}final class Loaded extends LoadState {  Loaded(this.items);  final List<String> items;}final class Failed extends LoadState {} String label(LoadState state) => switch (state) {      Loading => 'Loading…',      Loaded(:final items) => '${items.length} items',      _ => 'Something went wrong',    };

    Loading => is not a type test: a bare type name in a pattern is a constant pattern that compares the state with the Type object Loading using ==.

  16. 16

    Find the bug: some products from the API are rejected as invalid, although their JSON looks correct.

    class Product {  Product(this.name, this.price);  final String name;  final double price;} // `json` comes straight from `jsonDecode(response.body)`Product? parseProduct(Map<String, Object?> json) {  if (json case {'name': String name, 'price': double price}) {    return Product(name, price);  }  return null; // reported upstream as "invalid product"}

    The pattern requires price to be a double, but jsonDecode turns 12 into an int and only 12.5 into a double.

  17. 17

    Besides records and patterns, which Dart 3.x language features have you used, and what did each one replace?

    Dart 3.0 also brought class modifiers such as sealed and final; later releases added extension types (3.3), digit separators (3.6), wildcard _ variables (3.7), null-aware collection elements (3.8), dot shorthands (3.10) and primary constructors (3.13).

    // Needs `sdk: ^3.13.0` in pubspec.yaml.enum Side { start, center, end } class Point(final int x, final int y); // 3.13: constructor and fields in the header extension type UserId(String value) {} // 3.3: a zero-cost typed wrapper…
  18. 18

    What happens when you add a subtype to a public sealed class, and how do you ship that change safely?

    Every exhaustive switch over that type (in your code and in every package that depends on it) stops compiling until it handles the new case.

  19. 19

    What do records and patterns cost at runtime: what allocates, and what code runs during matching?

    Matching and destructuring are type tests and getter reads, which allocate nothing; creating a record allocates an object, like any constructor call.