ZetCode

Wildcard Variables in Dart

last modified May 25, 2025

Dart 3.8 introduces wildcard variables as a powerful new feature for pattern matching and destructuring. This tutorial explores how to use the underscore (_) as a wildcard to ignore values you don't need in various contexts.

Wildcard Variables Overview

In Dart, wildcard variables are represented by the underscore character (_). They allow you to match and ignore specific values in patterns, making your code cleaner and more expressive. This feature is particularly useful in destructuring assignments, pattern matching, and function parameters where certain values are not needed.

Use Case Description Example
Destructuring Ignore specific elements when unpacking var (x, _, z) = (1, 2, 3);
Pattern Matching Match but ignore values in patterns case [_, int middle, _]
Function Parameters Ignore unused parameters void log(String msg, _)
Loop Variables Ignore values in iterations for (var (_, value) in data)

The table above summarizes the main use cases for wildcard variables in Dart. Wildcards are versatile and can be applied in various contexts to improve code readability and maintainability. They help you focus on the values that matter while clearly indicating which values are intentionally ignored.

Basic Wildcard Usage

Wildcard variables are most commonly used in destructuring assignments where you want to extract only specific values from a collection or record. The underscore character serves as a visual indicator that certain values are intentionally being ignored.

basic_wildcard.dart
void main() {
  // Tuple destructuring with wildcard
  var (name, _, age) = ('Alice', 'alice@example.com', 30);
  print('$name is $age years old');
  
  // List destructuring
  var numbers = [1, 2, 3, 4, 5];
  var [first, _, third, ..._] = numbers;
  print('First: $first, Third: $third');
  
  // Map destructuring (with pattern matching)
  var user = {'name': 'Bob', 'email': 'bob@example.com', 'age': 25};
  var {'name': userName, 'age': userAge} = user;
  print('$userName is $userAge');
}

In these examples, the wildcard is used to skip email extraction from the tuple and to ignore even-numbered elements in the list. The map destructuring shows an alternative approach where you only extract the fields you need without explicitly ignoring others. The wildcard makes it immediately clear which values are being deliberately omitted from the destructuring operation.

$ dart run basic_wildcard.dart
Alice is 30 years old
First: 1, Third: 3
Bob is 25

Pattern Matching with Wildcards

Wildcards become particularly powerful when combined with Dart's pattern matching capabilities. They allow you to match complex structures while focusing only on the parts that matter for your logic.

pattern_matching.dart
void main() {
  // Matching lists with wildcards
  var matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
  ];
  
  for (var row in matrix) {
    switch (row) {
      case [_, var middle, _]:
        print('Middle value: $middle');
    }
  }
  
  // Matching records with nested wildcards
  var shapes = [
    ('circle', 10.0),
    ('square', 5.0),
    ('triangle', (3.0, 4.0, 5.0))
  ];
  
  for (var shape in shapes) {
    switch (shape) {
      case ('triangle', (_, var b, _)):
        print('Triangle middle side: $b');
      case (var type, _):
        print('Shape type: $type');
    }
  }
}

The first example demonstrates how wildcards can help extract only the middle element from each row of a matrix. The second example shows more advanced usage with nested patterns, where we use wildcards to ignore the shape name when we're only interested in triangle dimensions, and then ignore all dimensions when we just want the shape type. This selective matching makes the code both concise and expressive.

$ dart run pattern_matching.dart
Middle value: 2
Middle value: 5
Middle value: 8
Shape type: circle
Shape type: square
Triangle middle side: 4.0

Function Parameters and Wildcards

Wildcards can clean up function signatures when you need to accept parameters that you won't use. This is common with callback functions that follow a specific interface but don't need all provided arguments.

function_parameters.dart
void main() {
  // Function with ignored parameter
  void logError(String message, _) {
    print('ERROR: $message');
  }
  
  logError('File not found', 404);
  
  // Callback with ignored parameters
  void processItems(List<String> items, void callback(String, _)) {
    for (var i = 0; i < items.length; i++) {
      callback(items[i], i);
    }
  }
  
  processItems(['Apple', 'Banana', 'Cherry'], (item, _) {
    print('Processing $item');
  });
  
  // Higher-order function with wildcard
  void Function(String) makeLogger(String prefix) {
    return (String message) => print('$prefix: $message');
  }
  
  var debugLog = makeLogger('DEBUG');
  debugLog('Initializing');
}

The logError function shows a simple case where we accept but ignore a second parameter. The processItems example demonstrates a more realistic scenario where a callback receives an index parameter that it doesn't need. Using wildcards in these cases makes the code's intent clearer than using named parameters that would go unused. The higher-order function example shows how wildcards can help maintain clean function signatures even when dealing with function composition.

$ dart run function_parameters.dart
ERROR: File not found
Processing Apple
Processing Banana
Processing Cherry
DEBUG: Initializing

Advanced Wildcard Patterns

Wildcards can be combined with other Dart language features to create sophisticated patterns that remain readable and maintainable. These advanced patterns are particularly useful when working with complex data structures.

advanced_patterns.dart
void main() {
  // Nested destructuring with wildcards
  var complexData = [
    ('header', {'content': 'Hello', 'meta': 123}),
    ('footer', {'content': 'Goodbye', 'meta': 456})
  ];
  
  for (var (_, {'content': message}) in complexData) {
    print(message);
  }
  
  // Wildcards in switch expressions
  var responses = [
    (200, 'OK', {'data': 'content'}),
    (404, 'Not Found', null),
    (500, 'Error', {'error': 'message'})
  ];
  
  for (var response in responses) {
    var status = switch (response) {
      (200, _, _) => 'Success',
      (404, _, _) => 'Missing',
      (_, _, {'error': _}) => 'Error',
      _ => 'Unknown'
    };
    print(status);
  }
}

The first example shows nested pattern matching where we extract only the content field from within a map that's itself part of a tuple. The switch expression demonstrates how wildcards can help categorize responses based only on status codes while ignoring other fields. These patterns maintain clarity even when dealing with complex data structures.

$ dart run advanced_patterns.dart
Hello
Goodbye
Success
Missing
Error

While wildcards are powerful, they should be used judiciously to maintain code clarity. The primary purpose of wildcards is to explicitly ignore values that would otherwise appear to be accidentally unused. Overusing wildcards can make it harder to understand which parts of a data structure are actually being used in the logic.

Source

Dart Language: Patterns
Dart Language Evolution

Wildcard variables in Dart 3.8 provide a clean, expressive way to work with structured data while focusing only on the values that matter. By using the underscore character to explicitly ignore unneeded values, you can write more concise pattern matching code that clearly communicates its intent. This feature is particularly powerful when combined with Dart's other pattern matching capabilities.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.