ZetCode

Dart Variables

last modified June 4, 2025

In Dart, variables are used to store and manipulate data in programs. They act as named containers for values that can change during program execution.

Dart is statically typed but supports type inference, allowing both explicit and implicit variable declarations. Variables must be declared before use and can be initialized at declaration or later.

Variable Declaration and Initialization

Variables in Dart can be declared using the var keyword or explicit types. The var keyword lets Dart infer the type automatically based on the assigned value.

main.dart
void main() {
  // Using var with type inference
  var name = 'Alice';
  var age = 30;
  
  // Explicit type declaration
  String country = 'Canada';
  double height = 1.75;
  
  print('$name is $age years old from $country');
  print('Height: $height meters');
}

This example shows both inferred and explicit variable declarations. The var keyword is used when we want Dart to infer the type, while explicit types make the code more readable and enforce type safety.

$ dart main.dart
Alice is 30 years old from Canada
Height: 1.75 meters

Final and Const Variables

Dart provides final and const keywords for declaring variables that shouldn't change after initialization. final variables can only be set once, while const variables are compile-time constants.

main.dart
void main() {
  // final variable (runtime constant)
  final currentYear = DateTime.now().year;
  
  // const variable (compile-time constant)
  const pi = 3.14159;
  const maxAttempts = 3;
  
  print('Current year: $currentYear');
  print('PI value: $pi');
  print('Maximum attempts: $maxAttempts');
  
  // This would cause an error:
  // pi = 3.14;
}

final variables are initialized when accessed, while const variables must be initialized with constant values at compile time. Both prevent reassignment.

$ dart main.dart
Current year: 2025
PI value: 3.14159
Maximum attempts: 3

Dynamic and Object Types

Dart provides dynamic and Object types for variables that can hold any type. dynamic disables static type checking, while Object is the root of Dart's type system.

main.dart
void main() {
  dynamic dynamicValue = 'Hello';
  print('dynamicValue: $dynamicValue (${dynamicValue.runtimeType})');
  
  dynamicValue = 42;
  print('dynamicValue: $dynamicValue (${dynamicValue.runtimeType})');
  
  Object objectValue = 'World';
  print('objectValue: $objectValue (${objectValue.runtimeType})');
  
  // This would cause a runtime error:
  // print(objectValue.length);
  
  if (objectValue is String) {
    print('Length: ${objectValue.length}');
  }
}

dynamic allows changing type and accessing members without type checking. Object requires type checking before member access. Use dynamic sparingly as it reduces type safety.

$ dart main.dart
dynamicValue: Hello (String)
dynamicValue: 42 (int)
objectValue: World (String)
Length: 5

Nullable Variables

With null safety, variables must be explicitly declared as nullable using ? to hold null values. Non-nullable variables must always contain a value.

main.dart
void main() {
  // Non-nullable variable
  String name = 'Bob';
  // name = null; // Compile error
  
  // Nullable variable
  String? nickname = null;
  nickname = 'Bobby';
  
  print('Name: $name');
  print('Nickname: $nickname');
  
  // Null-aware operators
  int? age;
  int actualAge = age ?? 25;
  print('Age: $actualAge');
  
  // Null assertion operator (!)
  String? maybeString = 'Hello';
  print(maybeString!.length);
}

This demonstrates null safety features. The ?? operator provides default values for null cases, while ! asserts a value is non-null. Null safety helps prevent null reference errors.

$ dart main.dart
Name: Bob
Nickname: Bobby
Age: 25
5

Variable Scope

Variables in Dart have lexical scope, meaning they're only accessible within the block they're declared in. Global variables are accessible throughout the program.

main.dart
// Global variable
int globalCounter = 0;

void incrementCounter() {
  // Local variable
  int localCounter = 0;
  
  globalCounter++;
  localCounter++;
  
  print('Global: $globalCounter, Local: $localCounter');
}

void main() {
  incrementCounter();
  incrementCounter();
  
  // This would cause an error:
  // print(localCounter);
  
  print('Final global counter: $globalCounter');
}

globalCounter is accessible everywhere, while localCounter is only accessible within incrementCounter(). Each function call gets a new localCounter instance.

$ dart main.dart
Global: 1, Local: 1
Global: 2, Local: 1
Final global counter: 2

Wildcard Variables

Dart 3.8 introduces wildcard variables, allowing developers to use the underscore (_) as a placeholder for values they wish to ignore. Wildcard variables can be used in a variety of contexts, such as pattern matching, destructuring assignments, and function parameters, wherever a value is present but not needed. This feature makes code more concise, expressive, and self-documenting by clearly indicating which values are intentionally unused. Wildcards help eliminate unnecessary variable names and reduce clutter, making code easier to read and maintain.

Wildcard variables are especially powerful in pattern matching scenarios, such as switch statements, destructuring tuples or collections, and when defining callbacks or lambda functions where some parameters are not required. By using _, developers can focus on just the values they care about, while safely ignoring the rest. This not only avoids compiler warnings about unused variables, but also communicates intent to other developers reviewing the code.

main.dart
void main() {
  // Destructuring with wildcard
  var (a, _, c) = (1, 2, 3);
  print('a: $a, c: $c'); // Output: a: 1, c: 3

  // Pattern matching with wildcard
  var list = [10, 20, 30];
  switch (list) {
    case [_, int second, _]:
      print('Second element: $second');
  }

  // Function parameter wildcard
  void printFirst(int first, _) {
    print('First: $first');
  }
  printFirst(42, 'unused');

  // Using wildcards in for-each loops
  var pairs = [(1, 'one'), (2, 'two'), (3, 'three')];
  for (var (_, word) in pairs) {
    print(word); // Prints: one, two, three
  }

  // Ignoring multiple values in nested patterns
  var nested = (1, (2, 3));
  var (x, (_, z)) = nested;
  print('x: $x, z: $z'); // Output: x: 1, z: 3
}

In the example above, the underscore is used to ignore the second value during tuple destructuring, to match but ignore elements in a list pattern, and as a function parameter that is not used. Additional examples show how wildcards can be used in loops and nested patterns to further simplify code.

Best Practices

Source

Dart Variables Documentation

This tutorial covered Dart variables with practical examples demonstrating declaration, initialization, and various variable types in Dart.

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.