| |

Dart 6 🎯 Operators: Arithmetic, Relational, Type Test (is, as), and Bitwise

Operators are the symbols that perform computation on values. Dart provides the familiar arithmetic operators, comparison and equality operators, logical operators, and assignment operators that every C-family language has, plus a few that are distinctive: the type test and type cast operators (is and as), the null-aware operators (??, ?., ??=), and the cascade notation (..) that lets you call multiple methods on the same object. The type operators matter because Dart is statically typed and soundly null-safe; the null-aware operators matter because Dart has made null safety a core part of the language since version 2.12.

Understanding Dart’s operators is not just about memorizing symbols. It is about understanding the type system’s interaction with runtime values: when is performs a type check, when as performs a cast, when a null-aware operator short-circuits, and how the cascade notation avoids the temporary-variable pattern that plagues method chaining in other languages. These are the features that make Dart code concise and safe at the same time.

This chapter covers arithmetic operators, relational and equality operators, logical operators, type test and cast operators, bitwise operators, null-aware operators, assignment operators, and the cascade notation.

Key point: Dart’s operators include the standard arithmetic, relational, logical, and bitwise sets, plus type operators (is, as) for runtime type checks and casts, null-aware operators (??, ?., ??=) for safe null handling, and the cascade notation (..) for chaining calls on the same object.


Why these operators exist

The computation problem. Arithmetic and relational operators are the foundation of any expression. Dart follows the C-family conventions with +, -, *, /, %, ==, !=, <, >, <=, >=, so developers coming from other languages recognize them immediately.

The type safety problem. Dart is statically typed with sound null safety. The is operator lets you check a value’s runtime type before using it, and as performs a cast when the developer knows the type more precisely than the compiler. Without these, working with dynamic values, JSON, or polymorphic collections would require verbose workarounds.

The null problem. Null safety means a variable of type String? might be null and must be checked before use. The null-aware operators — ??, ?., ??= — provide concise ways to handle null without writing explicit if (x != null) blocks. This is a deliberate design choice that makes null handling part of the expression syntax rather than a separate statement.

The chaining problem. In many languages, configuring an object involves a temporary variable: var b = Builder(); b.setA(1); b.setB(2); return b.build();. The cascade notation in Dart writes the same thing without the temporary: Builder()..setA(1)..setB(2)..build(). This is used heavily in Flutter for widget configuration.

The bit manipulation problem. Systems programming and low-level data handling require bitwise operations. Dart provides the full set: &, |, ^, ~, <<, >>, >>>. The unsigned shift operator >>> is notable because Dart integers are signed by default, and >>> shifts without sign extension.


a. Arithmetic operators

Dart’s arithmetic operators follow standard conventions.

OperatorNameExampleResult
+Addition5 + 38
-Subtraction5 - 32
*Multiplication5 * 315
/Division10 / 33.3333...
~/Integer division10 ~/ 33
%Modulo10 % 31
-exprUnary negation-xNegated value
++Incrementx++, ++xIncrements by 1
--Decrementx--, --xDecrements by 1

The division operator / always produces a double, even when both operands are integers:

print(10 / 3);   // 3.3333333333333335
print(10 ~/ 3);  // 3

The integer division operator ~/ produces an int by truncating toward zero. Modulo % returns the remainder and works with both integers and doubles.

print(10 % 3);    // 1
print(10.5 % 3);  // 1.5

The increment and decrement operators have prefix and postfix forms. Prefix increments before returning the value; postfix returns the value then increments.

var a = 5;
print(a++);  // 5 (returns a, then increments)
print(a);    // 6

var b = 5;
print(++b);  // 6 (increments, then returns)

b. Relational and equality operators

OperatorMeaning
==Equal
!=Not equal
<Less than
>Greater than
<=Less than or equal
>=Greater than or equal

The equality operator == in Dart is a method on Object, which means classes can override it to define value equality. For most built-in types, == compares values. For custom classes, the default implementation compares identity unless == is overridden.

print(5 == 5);          // true
print(5 != 3);          // true
print('abc' == 'abc');  // true
print([1, 2] == [1, 2]); // false — different list instances

The list comparison returns false because List does not override == to compare elements. This is a common source of confusion. To compare lists by value, use listEquals from package:collection or compare element by element.

Dart’s == works correctly with null: null == null is true, and null == 5 is false.

c. Logical operators

OperatorMeaningExample
&&Logical ANDa && b
||Logical ORa || b
!Logical NOT!a

Logical operators work with bool values. && and || are short-circuit operators: the right operand is evaluated only if needed.

bool isAdult = age >= 18;
bool canVote = isAdult && isCitizen;
bool isWeekend = day == 'Sat' || day == 'Sun';
bool isInvalid = !isValid;

Short-circuit behavior:

// If x is null, the right side is not evaluated
if (x != null && x.length > 0) { ... }

// If the first condition is true, the second is not evaluated
if (isCached || fetchFromNetwork()) { ... }

d. Type test and cast operators

Dart provides two operators for working with types at runtime.

OperatorNamePurpose
isType testReturns true if the object is of the type
is!Negated type testReturns true if the object is not of the type
asType castCasts the object to the type, throws on failure

The is operator performs a type check:

var value = 'hello';

if (value is String) {
  print(value.toUpperCase());
}

if (value is! int) {
  print('Not an integer');
}

Dart’s type promotion means that after an is check, the variable is treated as the checked type within the block:

Object obj = 'hello';

if (obj is String) {
  print(obj.length);  // obj is promoted to String
}

The as operator performs an explicit cast:

var value = json['name'] as String;

If the cast fails at runtime, Dart throws a TypeError. The as operator is used when the developer knows the type more precisely than the compiler can infer, such as when parsing JSON.

A safer alternative to as is to check with is first:

if (value is String) {
  print(value.length);
} else {
  print('Not a string');
}

e. Bitwise operators

Bitwise operators work on the binary representation of integers.

OperatorNameExampleResult
&AND0b1100 & 0b10100b1000
|OR0b1100 | 0b10100b1110
^XOR0b1100 ^ 0b10100b0110
~NOT~0b1100-0b1101
<<Left shift1 << 38
>>Right shift16 >> 24
>>>Unsigned right shift-1 >>> 2815

The ~ operator flips all bits, producing a negative number in two’s complement representation. The << operator shifts bits left, multiplying by powers of two. The >> operator shifts right with sign extension, and >>> shifts right without sign extension.

print(0b1100 & 0b1010);  // 8
print(0b1100 | 0b1010);  // 14
print(1 << 4);           // 16
print(-8 >> 1);          // -4
print(-8 >>> 1);         // 9223372036854775804

Bitwise operators are commonly used for flags, permissions, and low-level data manipulation. The & operator combined with a mask isolates specific bits; | sets bits; ^ toggles bits.

f. Null-aware operators

Dart’s null safety means a nullable type (String?) may be null. The null-aware operators provide concise handling.

OperatorNameMeaning
??If-nullReturns left if not null, otherwise right
??=Null-aware assignmentAssigns only if the variable is null
?.Null-aware accessCalls or accesses only if not null

The ?? operator provides a default:

String? name;
print(name ?? 'Guest');  // Guest

name = 'Alice';
print(name ?? 'Guest');  // Alice

The ??= operator assigns only when the variable is null:

String? name;
name ??= 'Guest';   // assigns
name ??= 'Bob';     // does not assign; name is already 'Guest'
print(name);        // Guest

The ?. operator short-circuits method calls and property access:

String? name;
print(name?.length);  // null, no error

name = 'Alice';
print(name?.length);  // 5

Null-aware access can be chained:

user?.address?.city?.toUpperCase();

Each ?. short-circuits if the preceding value is null, so no step in the chain throws.

g. Assignment operators and cascade notation

Dart provides compound assignment operators for combining an operation with assignment.

OperatorEquivalent
+=x = x + y
-=x = x - y
*=x = x * y
/=x = x / y
~/=x = x ~/ y
%=x = x % y
&=x = x & y
|=x = x | y
^=x = x ^ y
<<=x = x << y
>>=x = x >> y
>>>=x = x >>> y
??=x = x ?? y

The cascade notation .. calls multiple methods on the same object and returns the object itself:

var list = [3, 1, 2]
  ..sort()
  ..add(4)
  ..remove(1);

print(list);  // [2, 3, 4]

Without cascade, this would require a temporary variable and separate statements:

var list = [3, 1, 2];
list.sort();
list.add(4);
list.remove(1);

The cascade is especially common in Flutter for building widget trees:

var paint = Paint()
  ..color = Colors.blue
  ..strokeWidth = 5.0
  ..style = PaintingStyle.stroke;

The cascade returns the Paint object, so it can be assigned or passed in the same expression.

A cascade can also use ?.. for null-aware cascading:

user?..name = 'Alice'
     ..age = 30;

The entire cascade is skipped if user is null.


Complete Example Session

// ============================================
// PART 1: ARITHMETIC OPERATORS
// ============================================
void main() {
  print(10 + 3);    // 13
  print(10 - 3);    // 7
  print(10 * 3);    // 30
  print(10 / 3);    // 3.333...
  print(10 ~/ 3);   // 3
  print(10 % 3);    // 1
}
// ============================================
// PART 2: INCREMENT AND DECREMENT
// ============================================
void main() {
  var a = 5;
  print(a++);  // 5
  print(a);    // 6

  var b = 5;
  print(++b);  // 6
  print(b);    // 6
}
// ============================================
// PART 3: RELATIONAL AND EQUALITY
// ============================================
void main() {
  print(5 == 5);        // true
  print(5 != 3);        // true
  print(5 > 3);         // true
  print(5 <= 5);        // true
  print(null == null);  // true
}
// ============================================
// PART 4: LOGICAL OPERATORS
// ============================================
void main() {
  var age = 25;
  var isCitizen = true;

  var canVote = age >= 18 && isCitizen;
  print(canVote);  // true

  var isWeekend = false;
  var isHoliday = true;
  print(isWeekend || isHoliday);  // true
}
// ============================================
// PART 5: TYPE TEST WITH is
// ============================================
void main() {
  Object value = 'hello';

  if (value is String) {
    print(value.length);  // 5 (value promoted to String)
  }

  if (value is! int) {
    print('Not an integer');
  }
}
// ============================================
// PART 6: TYPE CAST WITH as
// ============================================
void main() {
  Map<String, dynamic> json = {'name': 'Alice', 'age': 30};

  var name = json['name'] as String;
  var age = json['age'] as int;

  print('$name is $age years old');
}
// ============================================
// PART 7: BITWISE OPERATORS
// ============================================
void main() {
  const READ = 0x1;     // 0b0001
  const WRITE = 0x2;    // 0b0010
  const EXECUTE = 0x4;  // 0b0100

  var permissions = READ | WRITE;
  print(permissions & READ);     // 1 (has read)
  print(permissions & EXECUTE);  // 0 (no execute)

  permissions |= EXECUTE;
  print(permissions);  // 7
}
// ============================================
// PART 8: NULL-AWARE OPERATORS
// ============================================
void main() {
  String? name;

  print(name ?? 'Guest');  // Guest
  name ??= 'Alice';
  print(name);             // Alice
  print(name?.length);     // 5

  name = null;
  print(name?.length);     // null
}
// ============================================
// PART 9: COMPOUND ASSIGNMENT
// ============================================
void main() {
  var x = 10;
  x += 5;    // 15
  x -= 3;    // 12
  x *= 2;    // 24
  x ~/= 5;   // 4
  x <<= 2;   // 16

  print(x);  // 16
}
// ============================================
// PART 10: CASCADE NOTATION
// ============================================
void main() {
  var list = [3, 1, 2]
    ..sort()
    ..add(4)
    ..remove(1);

  print(list);  // [2, 3, 4]

  // Null-aware cascade
  List<int>? maybeList;
  maybeList?..add(1)..add(2);
  print(maybeList);  // null (entire cascade skipped)
}

These ten parts cover the full range of Dart operators: arithmetic, increment/decrement, relational and equality, logical, type test and cast, bitwise, null-aware, compound assignment, and cascade notation. Each pattern is idiomatic Dart.


Quick Reference

Arithmetic Operators

OperatorMeaningExample
+Addition5 + 3
-Subtraction5 - 3
*Multiplication5 * 3
/Division (double)10 / 3
~/Integer division10 ~/ 3
%Modulo10 % 3

Relational and Logical

OperatorMeaning
==, !=Equality
<, >, <=, >=Comparison
&&, ||, !Logical AND, OR, NOT

Type Operators

OperatorPurpose
isType test (with promotion)
is!Negated type test
asType cast (throws on failure)

Bitwise Operators

OperatorMeaning
&AND
|OR
^XOR
~NOT
<<Left shift
>>Signed right shift
>>>Unsigned right shift

Null-Aware Operators

OperatorMeaning
??Return left if not null, else right
??=Assign if null
?.Access if not null
?..Cascade if not null

Cascade Notation

SyntaxExample
..Paint()..color = red..strokeWidth = 2
?..user?..name = 'Alice'..age = 30

Best Practices

✅ Do This:

// Use is for type checks
if (value is String) print(value.length);

// Use ?? for defaults
var name = input ?? 'Guest';

// Use ?. for nullable access
print(user?.name?.length);

// Use cascade for configuration
var paint = Paint()
  ..color = Colors.blue
  ..strokeWidth = 2;

// Use bitwise for flags
var flags = READ | WRITE;

❌ Don’t Do This:

// Avoid as without a preceding check
var s = value as String;  // ❌ may throw

// Avoid unsafe null access
print(user!.name);        // ❌ crashes if user is null

// Avoid temp variables for configuration
var p = Paint();          // ❌ use cascade instead
p.color = Colors.blue;

Common Pitfalls

PitfallWhy It HappensFix
/ returns doubleDivision always produces doubleUse ~/ for integer result
List equality fails== compares identity for listsUse listEquals from collection
as throws TypeErrorValue is not the expected typeCheck with is before casting
Null check throwsUsing ! on a null valueUse ?. or ?? instead
Cascade reassignsUsing = instead of ..Use .. for cascades
?? vs ?:Dart has no ternary shorthand with nullUse ?? for null, ?: for condition

Real-World Examples

1. Safe Division

int average(List<int> nums) {
  if (nums.isEmpty) return 0;
  return nums.reduce((a, b) => a + b) ~/ nums.length;
}

2. Permission Flags

const READ = 1, WRITE = 2, EXECUTE = 4;
var perms = READ | WRITE;
bool canWrite = (perms & WRITE) != 0;

3. JSON Parsing with as

Map<String, dynamic> json = jsonDecode(response);
var name = json['name'] as String;
var age = json['age'] as int;

4. Type Check with Promotion

void printLength(Object value) {
  if (value is String) {
    print(value.length);  // promoted
  } else if (value is List) {
    print(value.length);  // promoted
  }
}

5. Default Value with ??

String getGreeting(String? name) {
  return 'Hello, ${name ?? 'Guest'}!';
}

6. Null-Aware Chain

var city = user?.address?.city ?? 'Unknown';

7. Null-Aware Assignment

var cache = <String, String>{};
String? cached;
cached ??= fetchFromNetwork();

8. Cascade Configuration

var controller = AnimationController(
  duration: Duration(seconds: 2),
  vsync: this,
)..addListener(() => setState(() {}))
 ..forward();

9. Null-Aware Cascade

StringBuffer? buffer;
buffer?..write('Hello')
      ..write(' ')
      ..write('World');

10. Bit Manipulation

int toggleBit(int value, int position) {
  return value ^ (1 << position);
}

Visual

Operator Categories

┌──────────────────────────────────────────────────────────────┐
│  DART OPERATOR CATEGORIES                                    │
│                                                              │
│  ARITHMETIC                                                  │
│  + - * / ~/ % ++ -- -expr                                    │
│                                                              │
│  RELATIONAL / EQUALITY                                       │
│  == != < > <= >=                                             │
│                                                              │
│  LOGICAL                                                     │
│  && || !                                                     │
│                                                              │
│  TYPE                                                        │
│  is is! as                                                   │
│                                                              │
│  BITWISE                                                     │
│  & | ^ ~ << >> >>>                                           │
│                                                              │
│  NULL-AWARE                                                  │
│  ?? ??= ?. ?..                                               │
│                                                              │
│  ASSIGNMENT                                                  │
│  = += -= *= /= ~/= %= &= |= ^= <<= >>= >>>= ??=              │
│                                                              │
│  CASCADE                                                     │
│  .. ?..                                                      │
└──────────────────────────────────────────────────────────────┘

Type Test and Promotion

┌──────────────────────────────────────────────────────────────┐
│  is PROMOTES THE TYPE WITHIN THE BLOCK                       │
│                                                              │
│  Object value = 'hello';                                     │
│                                                              │
│  if (value is String) {                                      │
│    // value is promoted to String here                       │
│    print(value.length);                                      │
│  }                                                           │
│                                                              │
│  Without promotion:                                          │
│  if (value is String) {                                      │
│    print((value as String).length);  // explicit cast       │
│  }                                                           │
│                                                              │
│  Promotion is why is is preferred over as when possible.     │
└──────────────────────────────────────────────────────────────┘

Null-Aware Operators

┌──────────────────────────────────────────────────────────────┐
│  ??        a ?? b       → a if a is not null, else b        │
│  ??=       a ??= b      → assign b to a if a is null        │
│  ?.        a?.b         → access b if a is not null         │
│  ?..       a?..b()      → cascade if a is not null          │
│                                                              │
│  String? name;                                               │
│  print(name ?? 'Guest');       // Guest                      │
│  name ??= 'Alice';             // name = 'Alice'             │
│  print(name?.length);          // 5                          │
│  print(name?.toUpperCase());   // ALICE                      │
└──────────────────────────────────────────────────────────────┘

Cascade Notation

┌──────────────────────────────────────────────────────────────┐
│  WITHOUT CASCADE              WITH CASCADE                   │
│                                                              │
│  var paint = Paint();         var paint = Paint()            │
│  paint.color = blue;            ..color = blue               │
│  paint.strokeWidth = 2;         ..strokeWidth = 2            │
│  paint.style = stroke;          ..style = stroke;            │
│                                                              │
│  The cascade returns the object itself, allowing             │
│  assignment or passing in the same expression.               │
│  Commonly used for widget configuration in Flutter.          │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
Arithmetic+, -, *, /, ~/, %
Integer division~/ returns int
Division result/ always returns double
Relational==, !=, <, >, <=, >=
Logical&&, ||, ! (short-circuit)
Type testis, is! (with type promotion)
Type castas (throws on failure)
Bitwise&, |, ^, ~, <<, >>, >>>
Null-aware??, ??=, ?., ?..
Cascade.., ?..
Compound assignment+=, -=, *=, /=, ~/=, %=, etc.

Key takeaways:

  • Dart’s arithmetic operators follow C conventions, with ~/ for integer division. The / operator always produces a double; ~/ truncates to an int .
  • The is operator performs a type test and promotes the variable. After if (value is String), the variable is treated as String within the block, so no explicit cast is needed.
  • The as operator performs a cast that throws on failure. Use it when the type is known more precisely than the compiler can infer, such as when parsing JSON. Prefer is when a check is acceptable.
  • Null-aware operators handle nullable values concisely. ?? provides a default, ??= assigns if null, and ?. accesses only if not null.
  • The cascade notation calls multiple methods on the same object. .. returns the object itself, eliminating the temporary variable pattern. It is heavily used in Flutter.
  • Bitwise operators work on integers. &, |, ^, ~, <<, >>, and >>> are available. The unsigned right shift >>> shifts without sign extension.
  • Compound assignment operators combine an operation with assignment. x += 5 is equivalent to x = x + 5, and the same pattern applies to all binary operators.

Remember: Dart’s operators are the symbols that make expressions possible, and the language’s distinctive additions — type test, null-aware, and cascade — reflect its priorities. Static typing with sound null safety means the compiler needs a way to express runtime type checks (is), casts (as), and null handling (??, ?.) within expressions rather than as separate statements. The cascade notation reflects the Flutter idiom of configuring objects in a single expression. Understanding these operators is understanding how Dart code reads: type checks that promote, null handling that short-circuits, and configuration that chains without temporary variables. The standard arithmetic, relational, logical, and bitwise operators are familiar; the null-aware and type operators are where Dart’s character shows.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!