Watch the video (10 min)
On this page25 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What Is Operator Precedence?
  5. 2. Parentheses Change the Order
  6. 3. Associativity: Same Level, Left to Right
  7. 4. Assignment Runs Last — and Right to Left
  8. 5. Unary Operators Bind Tightest
  9. 6. Arithmetic, Then Comparison, Then Logic
  10. 7. && Before ||
  11. 8. Comparisons Cannot Be Chained
  12. 9. ?? and ? : Sit Near the Bottom
  13. 10. The Precedence Table
  14. 11. Parentheses for Clarity
  15. 12. Custom Operators: A Preview
  16. 13. A Real-World Example
  17. Code Examples
  18. Common Beginner Mistakes
  19. Best Practices
  20. Mini Challenge
  21. Practice Questions
  22. Practice Answers
  23. Quick Revision
  24. Key Takeaways
  25. What's Next?

Lecture 21 — Operator Precedence & Custom Operators

Category4 — Operators
Lecture021 of 140
Estimated video length10 min
TargetDart 3.12.2
LevelBeginner

You now know dozens of operators. When several of them meet in one line, Dart needs a rule for which one goes first — and so do you, or 2 + 3 * 4 will surprise you.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways

Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.


Prerequisites

Students should already understand, from Lectures 004–020:

  • variables, int, double, bool, String, and string interpolation
  • int.tryParse and nullable types such as int? (Lectures 010 and 012)
  • the if-null operator ?? (Lecture 014)
  • arithmetic and assignment operators, including ~/, %, and += (Lecture 017)
  • comparison operators ==, !=, <, >, <=, >= (Lecture 018)
  • logical operators &&, ||, ! (Lecture 019)
  • ++, --, the conditional operator ? :, and the cascade .. (Lecture 020)

Connection With Previous Lectures

In Lectures 017–020 we mostly used one or two operators at a time. Real expressions mix them:

dart
int total = price * quantity + shipping;
bool canBuy = balance - price >= 0 && !isBlocked;

Which part does Dart calculate first? If it worked strictly left to right, 2 + 3 * 4 would be 20. It is actually 14. Until you know the rules, every long expression is a guess — and a wrong guess gives a wrong answer with no error message. This lecture gives you the rules, and shows the one tool that makes them easy to follow: parentheses.


Learning Objectives

By the end of this lecture you will be able to:

  • Explain what operator precedence and associativity mean
  • Predict the result of an expression that mixes several operators
  • Read Dart's precedence table for every operator taught so far
  • Use parentheses to change the order of evaluation
  • Use parentheses to make the order obvious, even when they are not required
  • Recognize the expressions Dart refuses to compile, and why
  • Describe what a custom operator is and what one looks like in a class
  • Avoid the most common precedence mistakes with ??, !, and ? :

1. What Is Operator Precedence?

Precedence is the rule that decides which operator runs first when several appear in one expression. An operator with higher precedence grabs its operands first.

Analogy — think of the order you get dressed: socks before shoes, no matter which you mention first. Dart's operators have the same kind of fixed order. * always "dresses" before +.

dart
void main() {
  print(2 + 3 * 4);
}
output
14

3 * 4 runs first and gives 12; then 2 + 12 gives 14.


2. Parentheses Change the Order

Anything inside parentheses ( ) is calculated first, whatever operators it contains.

dart
void main() {
  print(2 + 3 * 4);
  print((2 + 3) * 4);
}
output
14
20

The parentheses force 2 + 3 to become 5 before the multiplication. This is the same rule you learned in school maths.


3. Associativity: Same Level, Left to Right

When two operators have the same precedence, associativity decides. Most Dart operators are left-associative: they run from left to right.

dart
void main() {
  print(20 - 5 - 3);
  print(100 / 10 / 2);
  print(10 ~/ 3 * 3);
}
output
12
5.0
9

20 - 5 - 3 means (20 - 5) - 3, not 20 - (5 - 3). In the last line, ~/ and * share a level, so 10 ~/ 3 gives 3 first, and 3 * 3 gives 9.


4. Assignment Runs Last — and Right to Left

Assignment operators have the lowest precedence, so the whole right-hand side is finished before anything is stored. They are also right-associative:

dart
void main() {
  int total = 10;
  total *= 2 + 1;
  print(total);

  int x = 1;
  int y = 2;
  x = y = 7;
  print('$x $y');
}
output
30
7 7

total *= 2 + 1 means total = total * (2 + 1), which is 30 — not total * 2 + 1. x = y = 7 runs right to left: y gets 7 first, then x gets that same value.


5. Unary Operators Bind Tightest

Operators that work on a single value — -x, !x, ++x, x++ — sit near the top of the table. Postfix operators (written after the value) come first of all. That includes . for calling a method:

dart
void main() {
  print(-2.abs());
  print((-2).abs());
}
output
-2
2

-2.abs() means -(2.abs()): Dart calls abs() on 2 first, then applies the minus. Wrap the negative number in parentheses when you want its absolute value.


6. Arithmetic, Then Comparison, Then Logic

Arithmetic runs before comparison, and comparison runs before && and ||. That is why this needs no parentheses at all:

dart
void main() {
  int balance = 500;
  int price = 300;
  bool isBlocked = false;
  bool canBuy = balance - price >= 0 && !isBlocked;
  print(canBuy);
}
output
true

Dart reads it as ((balance - price) >= 0) && (!isBlocked): subtract, compare, then combine.


7. && Before ||

&& has higher precedence than ||, just as * is higher than +:

dart
void main() {
  int visits = 12;
  bool isMember = visits >= 10;
  bool hasCoupon = true;
  bool isWeekend = false;
  print(isMember || hasCoupon && isWeekend);
  print((isMember || hasCoupon) && isWeekend);
}
output
true
false

Here isMember is true (12 visits is at least 10). The first line means isMember || (hasCoupon && isWeekend). The parentheses in the second line change the meaning — and the answer.


8. Comparisons Cannot Be Chained

In maths you might write 0 < x < 10. Comparison and equality operators in Dart are non-associative: you cannot put two of them side by side.

dart
void main() {
  int a = 5;
  print(a < 10 < 20);
}
output
Error: A comparison expression can't be an operand of another comparison expression.
Try putting parentheses around one of the comparisons.
  print(a < 10 < 20);
               ^
Error: The operator '<' isn't defined for the type 'bool'.
Try correcting the operator to an existing operator, or defining a '<' operator.
  print(a < 10 < 20);
               ^

The second error explains the first: a < 10 is already a bool, and a bool cannot be compared with < 20.

To check a range, write two comparisons and join them with &&: a > 0 && a < 10.


9. ?? and ? : Sit Near the Bottom

The if-null operator ?? and the conditional operator ? : have lower precedence than arithmetic, comparison, and logic. So the arithmetic on their right is finished first:

dart
void main() {
  int? extra = int.tryParse('15');
  int score = extra ?? 0 + 10;
  print(score);
}
output
15

You might expect 25. But Dart reads extra ?? (0 + 10): since extra is 15, the + 10 never happens. To add 10 in every case, write (extra ?? 0) + 10.

The conditional operator is right-associative, so a chain of them is read from the right:

dart
void main() {
  int score = 72;
  String grade = score >= 90
      ? 'A'
      : score >= 70
      ? 'B'
      : 'C';
  print(grade);
}
output
B

This means score >= 90 ? 'A' : (score >= 70 ? 'B' : 'C'). It works, but it gets hard to read fast. Lecture 023 shows a clearer way to choose between many options.


10. The Precedence Table

Here is every operator from this category, from highest precedence to lowest:

LevelOperatorsNameAssociativity
1x++ x-- () [] . ?. x!postfix—
2-x !x ++x --xprefix (unary)—
3* / ~/ %multiplicativeleft
4+ -additiveleft
5< > <= >= is is! asrelational & type testnone
6== !=equalitynone
7&&logical ANDleft
8||logical ORleft
9??if-nullleft
10? :conditionalright
11.. ?..cascadeleft
12= += -= *= /= ~/= %= ??=assignmentright

Dart also has bitwise and shift operators (&, |, ^, ~, <<, >>, >>>); they sit between levels 4 and 5. This course does not need them, so we leave them aside.

Notice that the cascade .. is almost at the bottom, just above assignment. That is why, in Lecture 020, a whole cascade chain finished running before its result was stored in a variable.

You do not need to memorise all twelve rows. Remember three things: unary first, maths before comparisons, comparisons before logic — and use parentheses for everything else.


11. Parentheses for Clarity

Parentheses are also allowed when they change nothing. Use them whenever a reader might hesitate:

dart
void main() {
  int age = 16;
  bool hasTicket = true;
  bool withParent = false;

  bool canEnter = age >= 18 && hasTicket || withParent;
  bool clearer = (age >= 18 && hasTicket) || withParent;
  print(canEnter);
  print(clearer);
}
output
false
false

Both lines give the same answer, but the second one shows the intent at a glance. If an expression still feels crowded, split it into named variables — that is the clearest option of all.


12. Custom Operators: A Preview

Every operator so far worked on built-in types such as int and String. Dart also lets you define what an operator means for your own types. This is called operator overloading.

Your own types are made with classes, which we only begin in Lecture 052. So treat this as a preview: read it, run it, and don't worry about every keyword yet.

dart
class Money {
  final int amount;
  Money(this.amount);

  Money operator +(Money other) {
    return Money(amount + other.amount);
  }

  Money operator *(int times) {
    return Money(amount * times);
  }
}

void main() {
  Money lunch = Money(450);
  Money coffee = Money(150);
  Money total = lunch + coffee * 2;
  print(total.amount);

  total += Money(100);
  print(total.amount);
}
output
750
850

How it works

  1. class Money describes a new type that holds one int called amount.
  2. Money operator +(Money other) says: "when + sits between two Money values, add their amounts and give back a new Money."
  3. operator * does the same for Money * int.
  4. The precedence stays the same: coffee * 2 still runs before +, giving 450 + 300 = 750.
  5. Once + exists, += works for free.

You can define operators such as +, -, *, /, ~/, %, <, >, <=, >=, ==, and []. You cannot define &&, ||, !, ??, ? :, or = — their meaning is fixed.

We will build classes properly in Lectures 052–058, and redefine == for our own types when we cover Method Overriding (Lecture 060).


13. A Real-World Example

dart
void main() {
  int price = 450;
  int quantity = 3;
  int? coupon = int.tryParse('100');
  bool isMember = false;

  int subtotal = price * quantity - (coupon ?? 0);
  bool freeShipping = subtotal >= 1000 || isMember;
  int shipping = freeShipping ? 0 : 150;
  int total = subtotal + shipping;

  print('Subtotal: $subtotal');
  print('Free shipping: $freeShipping');
  print('Total: $total');
}
output
Subtotal: 1250
Free shipping: true
Total: 1250

Every precedence rule is at work: * before -, parentheses around ??, arithmetic before >=, comparison before ||, and the conditional operator picking a value before = stores it. Splitting the work into four named steps keeps each line easy to check.


Code Examples

Example 1 — Multiplication Before Addition

dart
void main() {
  print(10 + 4 * 2);
  print((10 + 4) * 2);
  print(10 - 6 / 2);
}
output
18
28
7.0

Example 2 — Left to Right at the Same Level

dart
void main() {
  print(50 - 20 - 10);
  print(50 - (20 - 10));
  print(60 / 6 * 2);
}
output
20
40
20.0

Example 3 — Remainder Shares a Level with *

dart
void main() {
  print(17 % 5 * 2);
  print(17 % (5 * 2));
  print(-3 % 5);
}
output
4
7
2

Example 4 — Unary Minus and a Method Call

dart
void main() {
  print(-7.abs());
  print((-7).abs());
  print(-7.abs() + 10);
}
output
-7
7
3

Example 5 — Comparison Before Logic

dart
void main() {
  int temperature = 28;
  bool isSunny = true;
  bool goOutside = temperature > 20 && temperature < 35 && isSunny;
  print(goOutside);
}
output
true

Example 6 — Compound Assignment Waits for the Right Side

dart
void main() {
  int points = 5;
  points += 3 * 2;
  print(points);
  points -= 4 - 1;
  print(points);
}
output
11
8

Example 7 — Student Marks

dart
void main() {
  int math = 80;
  int science = 75;
  int english = 85;

  double wrong = math + science + english / 3;
  double average = (math + science + english) / 3;
  print('Without parentheses: ${wrong.toStringAsFixed(2)}');
  print('Average: ${average.toStringAsFixed(2)}');
}
output
Without parentheses: 183.33
Average: 80.00

How it works

  1. In the first expression, / runs before +, so only english is divided by 3.
  2. The parentheses add all three marks first, then divide the total.
  3. Both lines compile — only the parentheses decide which answer is correct.

Example 8 — Shopping Total with an Optional Discount

dart
void main() {
  int price = 1200;
  int? discount = int.tryParse('');
  int shipping = 100;

  int total = price - (discount ?? 0) + shipping;
  print('Total: $total');
}
output
Total: 1300

How it works

  1. int.tryParse('') returns null, because empty text is not a number.
  2. (discount ?? 0) becomes 0; the parentheses keep ?? from swallowing the rest.
  3. Then 1200 - 0 + 100 runs left to right, giving 1300.

Example 9 — Bank Balance

dart
void main() {
  int balance = 2000;
  int withdrawal = 1700;
  int minimum = 500;
  bool isFrozen = false;

  bool allowed = (balance - withdrawal >= minimum) && !isFrozen;
  print('Withdrawal allowed: $allowed');
}
output
Withdrawal allowed: false

How it works

  1. balance - withdrawal gives 300 first.
  2. 300 >= 500 is false.
  3. !isFrozen is true, but false && true is false.
  4. The parentheses are not required — they just make the three steps visible.

Example 10 — Temperature Conversion

dart
void main() {
  double celsius = 30;
  double fahrenheit = celsius * 9 / 5 + 32;

  double hot = 104;
  double backToCelsius = (hot - 32) * 5 / 9;

  print('30 °C = $fahrenheit °F');
  print('104 °F = $backToCelsius °C');
}
output
30 °C = 86.0 °F
104 °F = 40.0 °C

How it works

  1. Celsius to Fahrenheit needs no parentheses: * and / run left to right, then + 32.
  2. Fahrenheit to Celsius does: 32 must be subtracted before multiplying.
  3. Without them, hot - 32 * 5 / 9 would subtract only 17.77…, giving a wrong answer.

Common Beginner Mistakes

1. Chaining comparisons like in maths

dart
// print(age > 0 < 120); // ✗
output
Error: A comparison expression can't be an operand of another comparison expression.

Write age > 0 && age < 120.

2. Forgetting that ?? comes after arithmetic

dart
void main() {
  int? bonus = int.tryParse('200');
  int salary = bonus ?? 0 + 1000; // reads as bonus ?? (0 + 1000)
  print(salary);
}
output
200

No error — just a wrong answer. Write (bonus ?? 0) + 1000.

3. Putting ! in front of a comparison

dart
// int count = 3;
// print(!count == 3); // ✗
output
Error: A value of type 'int' can't be assigned to a variable of type 'bool'.
  print(!count == 3);
         ^

! binds tighter than ==, so Dart tries !count first — and count is not a bool. Put the comparison in parentheses, or better, use !=:

dart
void main() {
  int count = 3;
  print(!(count == 3));
  print(count != 3);
}
output
false
false

4. Joining text before a conditional operator

dart
// int score = 72;
// print('Result: ' + score >= 50 ? 'Pass' : 'Fail'); // ✗
output
Error: A value of type 'int' can't be assigned to a variable of type 'String'.
  print('Result: ' + score >= 50 ? 'Pass' : 'Fail');
                     ^

+ runs first, so Dart tries 'Result: ' + score. Put the whole conditional inside interpolation:

dart
void main() {
  int score = 72;
  print('Result: ${score >= 50 ? 'Pass' : 'Fail'}');
}
output
Result: Pass

5. Expecting -2.abs() to be 2

The method call runs before the minus, so the result is -2. Write (-2).abs().

6. Leaving out parentheses in an average

a + b + c / 3 divides only c. Write (a + b + c) / 3.


Best Practices

  • Add parentheses whenever a reader might have to think about the order.
  • Always wrap ?? in parentheses when it is part of a larger calculation.
  • Split long expressions into named variables, one idea per line.
  • Keep ++ and -- on their own lines instead of inside bigger expressions.
  • Avoid chains of more than one ? : — later lectures teach clearer tools.
  • Prefer != over !( ... == ... ).
  • Test a tricky expression with small numbers you can check by hand.

Mini Challenge

Sara buys 3 notebooks at 120 each and one pen at 40. She has a coupon for 50 off the notebooks only. Write a single expression that calculates her total, using parentheses so the order is obvious, and print it.

Your program should print exactly:

output
Total: 350

Try it yourself before opening the solution.

Show solution
dart
void main() {
  int notebookPrice = 120;
  int notebooks = 3;
  int penPrice = 40;
  int coupon = 50;

  int total = (notebookPrice * notebooks - coupon) + penPrice;
  print('Total: $total');
}
output
Total: 350

How it works

  1. notebookPrice * notebooks runs first: 360.
  2. The coupon comes off the notebooks: 360 - 50 = 310.
  3. The pen is added last: 310 + 40 = 350. The parentheses aren't required here — they show that the coupon belongs to the notebooks.

Practice Questions

Work through these in order — they run from beginner to application to challenge. Predict each answer before you run the code.

Beginner

  1. Predict, then print: 8 + 2 * 5 and (8 + 2) * 5.
  2. Predict, then print: 20 - 6 - 4 and 20 - (6 - 4).
  3. Predict, then print: 17 ~/ 4 * 2 and 17 ~/ (4 * 2).

Application

  1. Store Zayan's marks 70, 85, and 90, then print their average as a double.
  2. Store isMember = false, hasCoupon = true, isWeekend = false. Predict and print isMember || hasCoupon && isWeekend, then the same with parentheses around isMember || hasCoupon.
  3. This program is meant to print 25, but prints 15. Fix it.
    dart
    int? extra = int.tryParse('15');
    int score = extra ?? 0 + 10;
  4. Convert 212 °F to Celsius with the formula (F − 32) × 5 ÷ 9 and print the result.
  5. Predict the values of a and b, then check: int a = 4; int b = a++ + ++a;

Challenge

  1. Ali can join a trip if he is at least 18 and has an ID, or if a parent comes with him. Store age = 15, hasId = true, withParent = true. Write the condition with parentheses that make the grouping clear, and print Can join: ….
  2. Copy the Money class from section 12 and add an operator - that subtracts two Money values. Use it to print the result of Money(1000) - Money(350) * 2.

Practice Answers

Question 1 — Solution

dart
void main() {
  print(8 + 2 * 5);
  print((8 + 2) * 5);
}
output
18
50

Question 2 — Solution

dart
void main() {
  print(20 - 6 - 4);
  print(20 - (6 - 4));
}
output
10
18

Question 3 — Solution

dart
void main() {
  print(17 ~/ 4 * 2);
  print(17 ~/ (4 * 2));
}
output
8
2

17 ~/ 4 is 4, then 4 * 2 is 8. With parentheses, 17 ~/ 8 is 2.

Question 4 — Solution

dart
void main() {
  int math = 70;
  int science = 85;
  int english = 90;
  double average = (math + science + english) / 3;
  print(average.toStringAsFixed(2));
}
output
81.67

Question 5 — Solution

dart
void main() {
  bool isMember = false;
  bool hasCoupon = true;
  bool isWeekend = false;
  print(isMember || hasCoupon && isWeekend);
  print((isMember || hasCoupon) && isWeekend);
}
output
false
false

Both are false, but for different reasons: the first is false || (true && false), the second is (false || true) && false.

Question 6 — Solution

dart
void main() {
  int? extra = int.tryParse('15');
  int score = (extra ?? 0) + 10;
  print(score);
}
output
25

Question 7 — Solution

dart
void main() {
  double fahrenheit = 212;
  double celsius = (fahrenheit - 32) * 5 / 9;
  print(celsius);
}
output
100.0

Question 8 — Solution

dart
void main() {
  int a = 4;
  int b = a++ + ++a;
  print('a: $a, b: $b');
}
output
a: 6, b: 10

a++ gives 4 and makes a equal 5. Then ++a makes a equal 6 and gives 6. So b is 4 + 6 = 10. Because ++ binds tighter than +, both increments happen before the addition — and code this hard to follow is exactly why ++ belongs on its own line.

Question 9 — Solution

dart
void main() {
  int age = 15;
  bool hasId = true;
  bool withParent = true;
  bool canJoin = (age >= 18 && hasId) || withParent;
  print('Can join: $canJoin');
}
output
Can join: true

Question 10 — Solution

dart
class Money {
  final int amount;
  Money(this.amount);

  Money operator +(Money other) {
    return Money(amount + other.amount);
  }

  Money operator -(Money other) {
    return Money(amount - other.amount);
  }

  Money operator *(int times) {
    return Money(amount * times);
  }
}

void main() {
  Money result = Money(1000) - Money(350) * 2;
  print(result.amount);
}
output
300

Your own * keeps the built-in precedence, so Money(350) * 2 runs first (700), and 1000 - 700 gives 300.


Quick Revision

  1. What does operator precedence decide?
  2. What does associativity decide?
  3. What is 2 + 3 * 4, and why?
  4. Which runs first: && or ||?
  5. Why does a < b < c not compile?
  6. How does Dart read x ?? 0 + 5?
  7. Why is -2.abs() equal to -2?
  8. Which operators have the lowest precedence of all?
  9. Does a custom * operator keep the precedence of the built-in *?

Key Takeaways

  • Precedence decides which operator runs first; associativity decides the order between operators on the same level.
  • Postfix and unary operators bind tightest; then * / ~/ %; then + -; then comparisons; then &&, ||, ??, ? :, cascade, and finally assignment.
  • Most operators are left-associative; ? : and assignment are right-associative.
  • Comparison and equality operators cannot be chained — join them with &&.
  • ?? runs after arithmetic, so wrap it in parentheses inside a calculation.
  • Parentheses override every rule, and are welcome even when they change nothing.
  • A class can define its own operators such as + and *; they keep the normal precedence.
  • &&, ||, !, ??, ? :, and = cannot be redefined.

What's Next?

Lecture 022 — if Statements. You can now build any condition you like and know exactly how Dart evaluates it. But so far a condition only produces true or false — the program still runs every line, every time. With if, a condition finally decides which code runs. That is where Category 5 — Control Flow begins.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.