Operators (Arithmetic, Comparison, Logical, Assignment)
What Are Operators?
Operators are special symbols that perform operations on values (called operands). You have already seen some operators in previous lessons -- now we will cover all of Dart’s operators in one comprehensive reference. Understanding operators is essential because you will use them in every single Dart program you write.
Arithmetic Operators
Arithmetic operators perform mathematical calculations. We covered the basics earlier, but let us now look at the complete picture including increment and decrement operators.
All Arithmetic Operators
void main() {
int a = 15;
int b = 4;
print(a + b); // 19 Addition
print(a - b); // 11 Subtraction
print(a * b); // 60 Multiplication
print(a / b); // 3.75 Division (always double)
print(a ~/ b); // 3 Integer division (truncates)
print(a % b); // 3 Modulo (remainder)
print(-a); // -15 Unary negation
}
Increment and Decrement
Dart provides ++ and -- operators to increase or decrease a value by 1. The position matters -- prefix vs postfix produces different behavior when used inside an expression.
Prefix vs Postfix
void main() {
// Prefix: changes value BEFORE using it
int a = 5;
int b = ++a; // a becomes 6 first, then b gets 6
print('a = \$a, b = \$b'); // a = 6, b = 6
// Postfix: changes value AFTER using it
int c = 5;
int d = c++; // d gets 5 first, then c becomes 6
print('c = \$c, d = \$d'); // c = 6, d = 5
// Same applies to decrement
int x = 10;
print(--x); // 9 (prefix: decrement then print)
print(x--); // 9 (postfix: print then decrement)
print(x); // 8 (now decremented)
}
count++; or ++count;), prefix and postfix behave identically. The difference only matters when the expression is part of a larger statement like an assignment or a print call.Assignment Operators
Assignment operators store a value in a variable. The basic assignment is =, but Dart provides compound assignment operators that combine an operation with assignment.
Basic and Compound Assignment
void main() {
// Basic assignment
int score = 100;
// Compound assignment operators
score += 10; // score = score + 10 -> 110
print(score);
score -= 20; // score = score - 20 -> 90
print(score);
score *= 2; // score = score * 2 -> 180
print(score);
score ~/= 3; // score = score ~/ 3 -> 60
print(score);
score %= 7; // score = score % 7 -> 4
print(score);
// Also works with doubles
double price = 100.0;
price /= 3; // price = price / 3 -> 33.333...
print(price.toStringAsFixed(2)); // 33.33
}
Null-Aware Assignment (??=)
The ??= operator assigns a value only if the variable is currently null. If it already has a value, the assignment is skipped.
Null-Aware Assignment
void main() {
int? a;
print(a); // null
a ??= 10; // a is null, so assign 10
print(a); // 10
a ??= 20; // a is NOT null (it is 10), so skip
print(a); // 10 (unchanged)
// Practical use: setting defaults
String? userName;
userName ??= 'Guest';
print(userName); // Guest
}
Comparison (Relational) Operators
Comparison operators compare two values and return a bool result. They are the foundation of all conditional logic.
All Comparison Operators
void main() {
int x = 10;
int y = 20;
print(x == y); // false Equal to
print(x != y); // true Not equal to
print(x > y); // false Greater than
print(x < y); // true Less than
print(x >= 10); // true Greater than or equal
print(x <= 20); // true Less than or equal
}
Comparing Different Types
Type-Specific Comparison
void main() {
// Strings compare by content
print('Dart' == 'Dart'); // true
print('Dart' == 'dart'); // false (case-sensitive)
// Numbers: int and double can be compared
print(5 == 5.0); // true
print(5.0 > 4); // true
// Booleans
print(true == true); // true
print(true == false); // false
// Lists compare by reference, NOT by content
var list1 = [1, 2, 3];
var list2 = [1, 2, 3];
print(list1 == list2); // false (different objects!)
}
== checks value equality for primitives (int, double, String, bool) but reference equality for objects like Lists and Maps. Two lists with the same contents are NOT equal with == unless they are the exact same object.Logical Operators
Logical operators combine boolean expressions. They are used extensively in conditions, loops, and validation logic.
Logical AND, OR, NOT
void main() {
bool isLoggedIn = true;
bool isAdmin = false;
bool hasPermission = true;
// AND (&&) -- ALL must be true
print(isLoggedIn && isAdmin); // false
print(isLoggedIn && hasPermission); // true
// OR (||) -- at least ONE must be true
print(isAdmin || hasPermission); // true
print(false || false); // false
// NOT (!) -- inverts the value
print(!isAdmin); // true
print(!isLoggedIn); // false
// Combining multiple operators
bool canEdit = isLoggedIn && (isAdmin || hasPermission);
print('Can edit: \$canEdit'); // Can edit: true
}
Short-Circuit Evaluation
Dart uses short-circuit evaluation for logical operators. This means it stops evaluating as soon as the result is determined:
Short-Circuit Behavior
bool checkFirst() {
print('Checking first...');
return false;
}
bool checkSecond() {
print('Checking second...');
return true;
}
void main() {
// AND: if first is false, second is never checked
print('--- AND ---');
bool resultAnd = checkFirst() && checkSecond();
// Output: "Checking first..." only
// checkSecond() is never called because false && anything = false
// OR: if first is true, second is never checked
print('--- OR ---');
bool resultOr = !checkFirst() || checkSecond();
// Output: "Checking first..." only
// checkSecond() is never called because true || anything = true
}
list != null && list.isNotEmpty is safe because if the list is null, the second check never runs and avoids a null error.Bitwise Operators
Bitwise operators work on the individual bits of integer values. While not used daily, they are important for performance-critical code, flags, and low-level programming.
Bitwise Operations
void main() {
int a = 5; // Binary: 0101
int b = 3; // Binary: 0011
print(a & b); // 1 AND (0001)
print(a | b); // 7 OR (0111)
print(a ^ b); // 6 XOR (0110)
print(~a); // -6 NOT (inverts all bits)
// Bit shifting
print(a << 1); // 10 Left shift (1010) -- multiply by 2
print(a >> 1); // 2 Right shift (0010) -- divide by 2
// Practical: using bit flags for permissions
const int READ = 1; // 001
const int WRITE = 2; // 010
const int EXECUTE = 4; // 100
int permissions = READ | WRITE; // 011 = 3
print(permissions & READ); // 1 (has read)
print(permissions & EXECUTE); // 0 (no execute)
// Check if permission exists
bool canRead = (permissions & READ) != 0;
print('Can read: \$canRead'); // Can read: true
}
Type Test Operators
Type test operators check or cast an object’s type at runtime:
is, is!, and as
void main() {
dynamic value = 'Hello, Dart!';
// is -- checks if object is of a type
if (value is String) {
print('It is a String with length ${value.length}');
}
// is! -- checks if object is NOT of a type
if (value is! int) {
print('It is NOT an int');
}
// as -- type cast (throws if wrong type)
num number = 42;
int integer = number as int;
print(integer); // 42
// Smart cast: after 'is' check, Dart auto-casts
dynamic data = 100;
if (data is int) {
// Inside this block, 'data' is automatically treated as int
print(data.isEven); // true -- no cast needed!
}
}
Conditional Operators
Conditional operators provide shorthand for if-else logic:
Ternary Operator (? :)
Ternary Operator
void main() {
int age = 20;
// condition ? valueIfTrue : valueIfFalse
String status = age >= 18 ? 'Adult' : 'Minor';
print(status); // Adult
// Can be nested (but keep it readable)
int score = 85;
String grade = score >= 90 ? 'A'
: score >= 80 ? 'B'
: score >= 70 ? 'C'
: 'F';
print(grade); // B
// Used in string interpolation
bool isOnline = true;
print('User is ${isOnline ? "online" : "offline"}');
}
Null-Aware Operators
Complete Null-Aware Operators
void main() {
// ?? -- null coalescing (provide default for null)
String? name;
String displayName = name ?? 'Guest';
print(displayName); // Guest
// ??= -- null-aware assignment
int? count;
count ??= 0;
print(count); // 0
// ?. -- null-aware member access
String? text;
print(text?.length); // null (no error!)
print(text?.toUpperCase()); // null (no error!)
text = 'Hello';
print(text?.length); // 5
// Chaining null-aware operators
String? city;
int nameLength = city?.length ?? 0;
print(nameLength); // 0
}
Cascade Operator (..)
The cascade operator lets you make multiple operations on the same object without repeating the variable name. It is incredibly useful for configuring objects.
Cascade Operations
void main() {
// Without cascade (repetitive)
var buffer1 = StringBuffer();
buffer1.write('Hello');
buffer1.write(' ');
buffer1.write('World');
buffer1.write('!');
print(buffer1); // Hello World!
// With cascade (clean and fluent)
var buffer2 = StringBuffer()
..write('Hello')
..write(' ')
..write('World')
..write('!');
print(buffer2); // Hello World!
// Null-aware cascade (?..)
String? text;
text
?..length // only runs if text is not null
;
// Practical: building a list
var numbers = <int>[]
..add(1)
..add(2)
..add(3)
..addAll([4, 5, 6]);
print(numbers); // [1, 2, 3, 4, 5, 6]
}
Spread Operator (...)
The spread operator expands a collection into individual elements. It is commonly used when building lists or combining collections.
Spread Operator
void main() {
var first = [1, 2, 3];
var second = [4, 5, 6];
// Combine lists with spread
var combined = [...first, ...second];
print(combined); // [1, 2, 3, 4, 5, 6]
// Add elements around spread
var withExtra = [0, ...first, 99];
print(withExtra); // [0, 1, 2, 3, 99]
// Null-aware spread (...?)
List<int>? maybeNull;
var safe = [1, 2, ...?maybeNull, 3];
print(safe); // [1, 2, 3]
}
Operator Precedence
When multiple operators appear in one expression, Dart follows a strict order of precedence. Here are the most important levels from highest to lowest:
Precedence Example
void main() {
// Multiplication before addition
print(2 + 3 * 4); // 14 (not 20)
print((2 + 3) * 4); // 20 (parentheses override)
// Comparison before logical
print(5 > 3 && 10 < 20); // true
// Evaluated as: (5 > 3) && (10 < 20) = true && true
// Assignment is lowest precedence
int x = 2 + 3; // x = 5 (addition happens first)
// When in doubt, use parentheses!
bool result = (5 + 3) > (2 * 3) && !(false || true);
print(result); // true && false = false
}
Practice Exercise
Open DartPad and create a program that: (1) Uses all compound assignment operators (+=, -=, *=, ~/=, %=) on a variable and prints after each step. (2) Demonstrates prefix vs postfix increment in two separate print statements showing different results. (3) Uses the ternary operator to assign a letter grade based on a numeric score. (4) Uses ?? and ??= operators to handle nullable values with defaults. (5) Uses the cascade operator (..) to build a list by chaining multiple .add() calls, then prints the result.