Skip to main content
The null-check pattern (?) is a refutable pattern matching construct in Dart that matches a value if and only if the runtime value is not null, subsequently binding the unwrapped value to a subpattern. It acts as a combined type promotion and destructuring mechanism, extracting non-nullable types from nullable expressions.

Syntax

The null-check pattern is denoted by appending a question mark (?) directly after a subpattern:
<subpattern>?

Mechanics

When a nullable value of type T? is evaluated against a null-check pattern:
  1. Evaluation: Dart checks the runtime value of the expression.
  2. Rejection: If the value is null, the pattern match fails.
  3. Extraction & Promotion: If the value is not null, Dart unwraps the value, promotes its static type from T? to T, and passes it to the inner <subpattern> for further matching or variable binding.

Refutable Contexts

Because the null-check pattern can fail (when the value is null), it is strictly a refutable pattern. It is valid only in refutable contexts, such as if-case statements, switch statements, and switch expressions. In these contexts, a null value simply causes the match to fail, allowing execution to fall through to the next case or bypass the conditional block.
String? nullableText = "Dart";

// Using if-case
if (nullableText case var text?) {
  // Match succeeds. 'text' is bound and promoted to non-nullable String.
  print(text.length); 
}

// Using switch statement
switch (nullableText) {
  case String s?: 
    // Matches any non-null String. 's' is non-nullable.
    break;
  case null:
    // Handles the null case explicitly.
    break;
}

Nested Destructuring

The null-check pattern is frequently nested within collection, object, or record patterns to enforce non-nullability on specific elements during destructuring.
(int?, int?) nullableCoordinates = (10, null);

if (nullableCoordinates case (var x?, var y?)) {
  // This match fails because the second element evaluates to null.
  // Both x and y must be non-null for the entire record pattern to match.
}

Irrefutable Contexts (Compile-Time Error)

The null-check pattern cannot be used in irrefutable contexts, such as standard variable declarations or assignments, because irrefutable contexts require patterns that are guaranteed to match. Attempting to use a null-check pattern in an irrefutable context results in a compile-time error.
int? maybeNumber = 42;

// COMPILE-TIME ERROR: Refutable patterns can't be used in an irrefutable context.
var (number?) = (maybeNumber); 
To achieve destructuring in an irrefutable context where a null value should throw a runtime exception, Dart provides the null-assert pattern (!). The null-assert pattern forces the match and throws if the value is null, making it valid for irrefutable declarations:
int? maybeNumber = 42;

// Valid: Uses null-assert (!), not null-check (?). 
// Throws a runtime exception if maybeNumber is null.
var (number!) = (maybeNumber); 
Tired of Poor Dart Skills? Fix That With Deep Grasping!Learn More