Operators are the tools you use to compute values, compare data, and make decisions. Dart's operator set covers the familiar arithmetic and relational operators found in most languages, and adds a powerful set of null-aware operators that are central to Dart's sound null safety system.
Arithmetic Operators
Mathematical operators perform standard numeric computations.
int a = 20;
int b = 7;
print(a + b); // 27 — addition
print(a - b); // 13 — subtraction
print(a * b); // 140 — multiplication
print(a / b); // 2.857... — division (returns double)
print(a ~/ b); // 2 — integer division (truncates decimal)
print(a % b); // 6 — modulo (remainder)
Assignment and Compound Assignment
int num = 10;
num += 5; // num = num + 5 --> 15
num -= 3; // num = num - 3 --> 12
num *= 2; // num = num * 2 --> 24
num ~/= 5; // num = num ~/ 5 --> 4
num %= 3; // num = num % 3 --> 1
Increment and Decrement
int x = 5;
print(x++); // 5 — post-increment (returns value, then increments)
print(x); // 6
print(++x); // 7 — pre-increment (increments, then returns value)
print(x); // 7
print(x--); // 7 — post-decrement
print(--x); // 5 — pre-decrement
Relational (Comparison) Operators
Relational operators compare two values and return a bool.
int num = 10;
print(num == 10); // true — equal to
print(num != 5); // true — not equal to
print(num > 5); // true — greater than
print(num < 20); // true — less than
print(num >= 10); // true — greater than or equal to
print(num <= 10); // true — less than or equal to
Ternary Operator
The ternary operator is a concise if-else expression.
int x = 100;
var label = x % 2 == 0 ? 'Even' : 'Odd';
print(label); // Even
// Equivalent to:
// if (x % 2 == 0) { label = 'Even'; } else { label = 'Odd'; }
Type Test Operator
The is operator checks whether a value is an instance of a given type.
dynamic value = 42;
if (value is int) {
print('Is an integer'); // Prints this
}
if (value is! String) {
print('Is not a string'); // Prints this
}
Logical Operators
Logical operators combine boolean expressions.
int age = 25;
bool isStudent = true;
// AND — both conditions must be true
if (age >= 18 && age < 30) {
print('Young adult');
}
// OR — at least one condition must be true
if (age < 18 || isStudent) {
print('Gets a discount');
}
// NOT — inverts a boolean
if (!isStudent) {
print('Full price');
}
Short-Circuit Evaluation
Dart evaluates logical expressions lazily:
// If the first condition of && is false, the second is never evaluated
if (user != null && user.isActive) {
// ...
}
// If the first condition of || is true, the second is never evaluated
if (cachedValue != null || loadFromDisk()) {
// ...
}
Bitwise Operators
Bitwise operators manipulate individual bits of integer values.
int flags = 0x0F; // 00001111
print(flags & 0x03); // AND --> 00000011 (3)
print(flags | 0xF0); // OR --> 11111111 (255)
print(flags ^ 0xFF); // XOR --> 11110000 (240)
print(~flags); // NOT --> flips all bits
print(flags << 2); // left shift --> 00111100 (60)
print(flags >> 2); // right shift --> 00000011 (3)
Null-Aware Operators
Null-aware operators are one of Dart's most useful features. They provide concise, safe ways to handle null values.
Null-Check Access `?.`
The ?. operator accesses a property or calls a method only if the object is not null. If the object is null, the entire expression evaluates to null instead of throwing.
String? name;
// Without null-aware operator (verbose)
int? length = name != null ? name.length : null;
// With null-aware operator (concise)
int? length = name?.length;
// Chain multiple null-aware accesses
String? city = user?.address?.city;
Null-Coalescing `??`
The ?? operator returns the left operand if it is not null, otherwise returns the right operand.
String? username;
String displayName = username ?? 'Guest'; // 'Guest' because username is null
int count = null ?? 0; // 0
int score = 95 ?? 0; // 95 (not null, so left side wins)
Null-Coalescing Assignment `??=`
The ??= operator assigns a value to a variable only if the variable is currently null. If it already has a value, nothing happens.
String? config;
config ??= 'default-config'; // Assigns because config is null
print(config); // default-config
config ??= 'other-value'; // Does NOT assign because config already has a value
print(config); // default-config (unchanged)
Null Assertion `!`
The ! postfix operator asserts that a value is not null. If it is null, a runtime exception is thrown. Use only when you are certain the value cannot be null.
String? name = 'Amr';
String definitelyNotNull = name!; // Safe here because we know the value
// Dangerous:
String? maybeNull;
// String value = maybeNull!; // Throws: Null check operator used on a null value
Cascade Operator `..`
The cascade operator allows you to make a sequence of operations on the same object without repeating the variable name.
// Without cascade
var buffer = StringBuffer();
buffer.write('Hello');
buffer.write(', ');
buffer.write('Dart!');
// With cascade (more concise)
var buffer = StringBuffer()
..write('Hello')
..write(', ')
..write('Dart!');
print(buffer.toString()); // Hello, Dart!
Spread Operator `...` and `...?`
The spread operator inserts all elements of a collection into another collection.
List<int> first = [1, 2, 3];
List<int> second = [4, 5, 6];
List<int> combined = [...first, ...second];
print(combined); // [1, 2, 3, 4, 5, 6]
// Null-aware spread (safe if the list might be null)
List<int>? maybeList;
List<int> safe = [...?maybeList, 7, 8];
print(safe); // [7, 8]
Operator Summary
Arithmetic: + - * / ~/ %
Assignment: = += -= *= ~/= %=
Increment: ++ --
Comparison: == != > < >= <=
Ternary: condition ? ifTrue : ifFalse
Type test: is is!
Logical: && || !
Bitwise: & | ^ ~ << >>
Null-aware: ?. ?? ??= !
Cascade: .. ?..
Spread: ... ...?
Mastering the null-aware operators in particular will make your Dart code significantly cleaner and more robust. They are used constantly in real Flutter applications wherever data might be absent.