ZetCode

Dart Expressions

last modified June 4, 2025

Expressions form the core of Dart programs, enabling computations that yield values. This tutorial explores Dart expressions, detailing operator types, evaluation rules, and unique features like cascade notation.

Dart Expressions Overview

In Dart, an expression is a code construct that evaluates to a value, such as literals, variables, operators, or function calls. Dart offers a diverse set of operators and expression types, facilitating concise and expressive code for various programming tasks.

Expression Type Description Etitle="Dart Code Example
Literal Direct value representation 5, 'hello', true
Arithmetic Mathematical operations a + b, x * y
Relational Comparison operations a > b, x == y
Logical Boolean operations a && b, !flag
Conditional Ternary operator a ? b : c
Cascade Sequential operations obj..a()..b()
Assignment Value assignment a = 5, b += 2

Literal Expressions

Literal expressions directly represent fixed values in code, supporting various data types in Dart, including integers (e.g., 42), doubles (e.g., 3.14), strings (using single, double, or triple quotes), booleans (true, false), lists, maps, and the null value. These literals provide a straightforward way to embed constant values in programs.

literals.dart
void main() {

  // Numeric literals
  int integer = 42;
  double floating = 3.14159;

  // String literals
  String singles = 'Single quotes';
  String doubles = "Double quotes";
  String multiline = '''
    Multi-line
    string
  ''';

  // Boolean literals
  bool truthy = true;
  bool falsy = false;

  // List and Map literals
  List<int> numbers = [1, 2, 3];
  Map<String, int> ages = {'Alice': 25, 'Bob': 30};

  print(integer);
  print(floating);
  print(singles);
  print(doubles);
  print(multiline);
  print(truthy);
  print(falsy);
  print(numbers);
  print(ages);
}

This example showcases various Dart literals, demonstrating how to define and print integers, doubles, strings (including multiline), booleans, lists, and maps, highlighting their direct use in code.

$ dart run literals.dart
42
3.14159
Single quotes
Double quotes
    Multi-line
    string
  
[1, 2, 3]
{Alice: 25, Bob: 30}

Arithmetic Expressions

Dart supports standard arithmetic operations through operators like addition (+), subtraction (-), multiplication (*), division (/), integer division (~/), modulo (%), and unary negation (-). The division operator always returns a double, while increment (++) and decrement (--) operators modify values. These operators enable mathematical computations, including mixed-type operations.

arithmetic.dart
void main() {

  int a = 10;
  int b = 3;
  double c = 5.0;
  
  // Basic operations
  print('Addition: ${a + b}');
  print('Subtraction: ${a - b}');
  print('Multiplication: ${a * b}');
  print('Division: ${a / b}');
  print('Integer division: ${a ~/ b}');
  print('Modulo: ${a % b}');
  print('Unary minus: ${-a}');
  
  // Mixed type arithmetic
  print('Mixed addition: ${a + c}');
  
  // Increment/decrement
  int counter = 0;
  print('Post-increment: ${counter++}');
  print('After increment: $counter');
  print('Pre-decrement: ${--counter}');
}

This example demonstrates arithmetic operations, including basic calculations, mixed-type addition, and increment/decrement operations, illustrating how Dart handles mathematical expressions.

$ dart run arithmetic.dart
Addition: 13
Subtraction: 7
Multiplication: 30
Division: 3.3333333333333335
Integer division: 3
Modulo: 1
Unary minus: -10
Mixed addition: 15.0
Post-increment: 0
After increment: 1
Pre-decrement: 0

Relational and Logical Expressions

Relational operators, such as equal (==), not equal (!=), greater than (>), and less than (<), compare values to produce boolean results. Logical operators, including AND (&&), OR (||), and NOT (!), combine boolean expressions. Dart employs short-circuit evaluation, skipping unnecessary operations (e.g., in && if the first operand is false), enhancing efficiency and safety in logical expressions.

relational_logical.dart
void main() {

  int a = 5;
  int b = 10;
  
  // Relational operators
  print('Equal: ${a == b}');
  print('Not equal: ${a != b}');
  print('Greater than: ${a > b}');
  print('Less than: ${a < b}');
  print('Greater or equal: ${a >= b}');
  print('Less or equal: ${a <= b}');
  
  // Logical operators
  bool x = true;
  bool y = false;
  
  print('AND: ${x && y}');
  print('OR: ${x || y}');
  print('NOT: ${!x}');
  
  // Short-circuit evaluation
  String? name;
  bool isValid = name != null && name.isNotEmpty;
  print('Is valid: $isValid');
}

This example illustrates relational comparisons and logical operations, including a demonstration of short-circuit evaluation with a nullable string, showing how Dart ensures safe and efficient expression evaluation.

$ dart run relational_logical.dart
Equal: false
Not equal: true
Greater than: false
Less than: true
Greater or equal: false
Less or equal: true
AND: false
OR: true
NOT: false
Is valid: false

Conditional Expressions

Dart's conditional expressions provide concise decision-making constructs. The ternary operator (?:) acts as a compact if-else statement, while the null- coalescing operator (??) supplies default values for null variables. Null-aware operators, such as conditional property access (?.) and null-aware assignment (??=), ensure safe handling of nullable types, making code robust and succinct.

conditional.dart
void main() {

  int age = 20;
  
  // Ternary operator
  String status = age >= 18 ? 'Adult' : 'Minor';
  print('Status: $status');
  
  // Null-coalescing operator
  String? name;
  String displayName = name ?? 'Guest';
  print('Welcome, $displayName');
  
  // Conditional property access
  List<int>? numbers;
  int? first = numbers?.first;
  print('First number: $first');
  
  // Null-aware assignment
  name ??= 'Anonymous';
  print('Name: $name');
}

This example showcases conditional expressions, including the ternary operator for age-based status, null-coalescing for default names, and null-aware operators for safe list access and assignment, demonstrating concise control flow.

$ dart run conditional.dart
Status: Adult
Welcome, Guest
First number: null
Name: Anonymous

Cascade Notation

Dart's cascade notation (..) enables multiple operations on the same object in a fluent manner, returning the original object after each operation. This feature is particularly useful for builder-style APIs and simplifies code by reducing repetitive object references, enhancing readability and maintainability in sequential method calls.

cascade.dart
class Person {
  String name = '';
  int age = 0;
  
  void greet() => print('Hello, $name!');
  void birthday() => age++;
}

void main() {

  // Without cascade
  Person p1 = Person();
  p1.name = 'Alice';
  p1.age = 30;
  p1.greet();
  
  // With cascade
  Person p2 = Person()
    ..name = 'Bob'
    ..age = 25
    ..greet()
    ..birthday();
    
  print('Bob\'s age: ${p2.age}');
  
  // Nested cascades
  StringBuffer sb = StringBuffer()
    ..write('Hello')
    ..write(' ')
    ..writeAll(['Dart', '!'], ' ');
  
  print(sb.toString());
}

This example contrasts traditional and cascade notation for object operations, using a Person class and StringBuffer to show how cascades streamline multiple method calls and property assignments.

$ dart run cascade.dart
Hello, Alice!
Hello, Bob!
Bob's age: 26
Hello Dart !

Assignment Expressions

Assignment expressions in Dart store values in variables using the basic assignment operator (=) or compound operators like +=, *=, and ~/=. These expressions evaluate to the assigned value, enabling chained assignments. Null-aware assignment (??=) assigns a value only if the variable is null, providing a safe way to set defaults.

assignment.dart
void main() {

  // Simple assignment
  int x = 5;
  print('x = $x');
  
  // Compound assignment
  x += 3;
  print('x += 3 → $x');
  
  x ~/= 2;
  print('x ~/= 2 → $x');
  
  // Assignment as expression
  int y;
  print('y = ${y = x * 2}');
  
  // Null-aware assignment
  int? z;
  z ??= 10;
  print('z = $z');
}

This example demonstrates simple and compound operations, assignment as an expression, and null-aware assignment, illustrating how Dart manages variable updates and default values.

$ dart run assignment.dart
x = 5
x += 3 → 8
x ~/= 2 → 4
y = 8
z = 10

Expression Evaluation Order

Dart evaluates expressions based on operator precedence and associativity. Operators like multiplication have higher precedence than addition, while parentheses override default precedence. Most operators are left-associative, processing from left to right, except for assignment and conditional operators, which are right-associative rules, ensuring predictable computation order.

evaluation_order.dart Copy
void main() {

  // Operator precedence
  int result = 2 + 3 * 4;
  print('2 + 3 * 4 = $result');
  
  // Parentheses change order
  result = (2 + 3) * 4;
  print('(2 + 3) * 4 = $result');
  
  // Left-associative operators
  result = 10 - 4 - 2;
  print('10 - 4 - 2 = $result');
  
  // Right-associative operators
  bool a = false, b = true, c = false;
  bool logical = a && b || c;
  print('a && b || c = $logical');
}

This example illustrates operator precedence, the effect of parentheses, and associativity in expression evaluation, showing how Dart processes complex expressions systematically.

$ dart run evaluation_order.dart
2 + 3 * 4 = 14
(2 + 3) * 4 = 20
10 - 4 - 2 = 4
a && b || c = false

Bitwise Expressions

Dart's bitwise operators manipulate integer bits, enabling low-level operations like setting flags or optimizing computations. Operators include AND (&), OR (|), XOR (^), NOT (~), left shift (<<), and right shift (>>). These are useful in scenarios like graphics processing or protocol implementations, where direct bit manipulation is required.

bitwise.dart
void main() {

  int a = 5; // Binary: 0101
  int b = 3; // Binary: 0011
  
  // Bitwise operators
  print('Bitwise AND: ${a & b}'); // 0101 & 0011 = 0001
  print('Bitwise OR: ${a | b}'); // 0101 | 0011 = 0111
  print('Bitwise XOR: ${a ^ b}'); // 0101 ^ 0011 = 0110
  print('Bitwise NOT: ${~a}'); // ~0101 = ...1010 (inverts bits)
  print('Left shift: ${a << 1}'); // 0101 << 1 = 1010
  print('Right shift: ${a >> 1}'); // 0101 >> 1 = 0010
  
  // Using bitwise for flags
  int read = 1; // 0001
  int write = 2; // 0010
  int permissions = read | write; // 0011
  print('Has read: ${(permissions & read) != 0}');
}

This example demonstrates bitwise operations on integers, showing AND, OR, XOR, NOT, and shift operations, along with a practical use case for managing permission flags, highlighting their utility in low-level programming.

$ dart run bitwise.dart
Bitwise AND: 1
Bitwise OR: 7
Bitwise XOR: 6
Bitwise NOT: -6
Left shift: 10
Right shift: 2
Has read: true

Spread Operator Expressions

The spread operator (...) in Dart expands elements of a collection, enabling concise merging or copying of lists, sets, or maps. The null-aware spread operator (...?) safely handles null collections, preventing runtime errors. This feature simplifies collection manipulation in expressions, enhancing code readability and flexibility.

spread.dart
void main() {

  // Spread operator with lists
  List<int> part1 = [1, 2];
  List<int> part2 = [3, 4];
  List<int> combined = [...part1, ...part2, 5];
  print('Combined list: $combined');
  
  // Null-aware spread
  List<int>? optional;
  List<int> safeList = [...part1, ...?optional, 6];
  print('Safe list: $safeList');
  
  // Spread with maps
  Map<String, int> map1 = {'a': 1, 'b': 2};
  Map<String, int> map2 = {'c': 3};
  Map<String, int> merged = {...map1, ...map2, 'd': 4};
  print('Merged map: $merged');
  
  // Nested spread
  List<List<int>> nested = [
    [1, 2],
    [3, 4]
  ];
  List<int> flattened = [...nested[0], ...nested[1]];
  print('Flattened: $flattened');
}

This example showcases the spread operator for combining lists and maps, the null-aware spread for safe handling of nullable collections, and flattening nested lists, demonstrating its power in collection expressions.

$ dart run spread.dart
Combined list: [1, 2, 3, 4, 5]
Safe list: [1, 2, 6]
Merged map: {a: 1, b: 2, c: 3, d: 4}
Flattened: [1, 2, 3, 4]

Switch Expressions

Dart's switch expressions provide a concise way to select a value based on multiple conditions. Unlike traditional switch statements, switch expressions can be used directly in assignments and return values, making code more expressive and reducing boilerplate. Dart supports pattern matching and null safety in switch expressions, allowing for powerful and readable branching logic.

switch_expression.dart
void main() {

  var grade = 'B';
  var result = switch (grade) {
    'A' => 'Excellent',
    'B' => 'Good',
    'C' => 'Average',
    'D' => 'Below average',
    'F' => 'Fail',
    _ => 'Unknown',
  };
  print('Grade: $grade, Result: $result');

  // Pattern matching with types
  Object value = 42;
  var type = switch (value) {
    int i => 'Integer',
    double d => 'Double',
    String s => 'String',
    _ => 'Other',
  };
  print('Type: $type');
}

This example demonstrates Dart's switch expressions for value selection and type pattern matching. The first switch maps a grade to a description, while the second uses type patterns to determine the type of a value. Switch expressions improve code clarity and reduce the need for verbose if-else chains.

Source

Dart Language Operators
Dart Language Tour: Expressions

Mastering Dart expressions is essential for crafting efficient and expressive code. From literals to advanced features like cascade and spread operators, Dart offers versatile tools for diverse programming needs.

Author

My name is Jan Bodnar, a passionate programmer with extensive experience. Since 2007, I have authored over 1,400 articles and 8 e-books, with more than ten years of teaching programming.

List all Dart tutorials.