Skip to main content
The null-check pattern matches a value if it is not null and subsequently matches an inner pattern against that non-nullable value. Syntactically represented by a trailing question mark (?), it allows for safe extraction and type promotion within pattern matching contexts, such as switch statements, switch expressions, and if-case constructs.

Syntax

The null-check pattern consists of a subpattern followed immediately by ?.
subpattern?

Semantics and Execution

When the Dart runtime evaluates a null-check pattern against a subject value (the scrutinee):
  1. Null Validation: The runtime checks if the subject value is null.
  2. Match Failure: If the subject is null, the pattern fails to match. Execution continues to the next case or branch. It does not throw an exception.
  3. Type Promotion: If the subject is not null, the value is treated as its non-nullable counterpart.
  4. Subpattern Evaluation: The subpattern is then matched against the promoted, non-nullable value.

Variable Binding

The null-check pattern is frequently combined with a variable declaration pattern to bind the non-nullable value to a new variable.
String? nullableValue = "Dart";

switch (nullableValue) {
  // 1. Checks if nullableValue is not null.
  // 2. Promotes it to String (non-nullable).
  // 3. Binds the value to variable 's'.
  case var s?: 
    print(s.toUpperCase()); // 's' is type String, not String?
  
  case null:
    print("Value was null");
}

Recursive Composition

The null-check pattern can be nested within other patterns or contain other patterns. This allows for deep validation of nullable structures.
List<int?>? nullableList = [1, 2, null];

switch (nullableList) {
  // Matches if the list itself is not null AND the first element is not null
  case [var firstElement?, ...]:
    print("First element is $firstElement"); 
  default:
    print("No match");
}

Distinction from Null-Assert Pattern

The null-check pattern (?) must be distinguished from the null-assert pattern (!).
  • Null-Check (?): If the value is null, the match fails safely. Control flow moves to the next case.
  • Null-Assert (!): If the value is null, the runtime throws a TypeError.
String? value = null;

switch (value) {
  case var s?: // Safe: Match fails, moves to default
    print(s);
  default:
    print("Did not match");
}

switch (value) {
  case var s!: // Unsafe: Throws TypeError immediately
    print(s);
}
Master Dart with Deep Grasping Methodology!Learn More