Watch the video (10 min)
On this page24 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What Are Logical Operators?
  5. 2. AND — &&
  6. 3. The AND Truth Table
  7. 4. OR — || and Its Truth Table
  8. 5. NOT — !
  9. 6. Combining Comparisons
  10. 7. Only bool Values Allowed
  11. 8. Mixing &&, ||, and !
  12. 9. Negating a Whole Condition
  13. 10. Short-Circuit Evaluation
  14. 11. Short-Circuit with Nullable Values
  15. 12. A Real-World Example
  16. Code Examples
  17. Common Beginner Mistakes
  18. Best Practices
  19. Mini Challenge
  20. Practice Questions
  21. Practice Answers
  22. Quick Revision
  23. Key Takeaways
  24. What's Next?

Lecture 19 — Logical Operators

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

A comparison answers one question. Real rules ask several at once: "has a ticket and is old enough", "is a member or spent enough". Logical operators join true/false answers into one.

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 001–018:

  • var, final, and the bool type with its two values, true and false
  • string interpolation, string indexes, and isNotEmpty
  • nullable types such as int?, int.tryParse, and the ?? operator
  • arithmetic operators, including % and ~/
  • comparison operators: ==, !=, <, >, <=, >=

Connection With Previous Lectures

In Lecture 018, every comparison answered a single question:

dart
print(age >= 18);

But real rules are rarely that simple. A cinema lets you in if you have a ticket and you are old enough. A shop gives free delivery if you are a member or your order is large. With comparisons alone we can ask each part, but we cannot join the answers into one true or false. Logical operators do exactly that.


Learning Objectives

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

  • Use && to require that every condition is true
  • Use || to accept when at least one condition is true
  • Use ! to flip a bool to its opposite
  • Read and write the truth table for each operator
  • Combine comparisons into range checks and multi-part rules
  • Explain short-circuit evaluation and use it to avoid crashes
  • Use && safely with nullable values
  • Recognize the error messages Dart produces for common logical-operator mistakes

1. What Are Logical Operators?

A logical operator takes bool values and produces a new bool. Dart has three:

OperatorNameMeanstrue when…
&&ANDbothboth sides are true
||OReitherat least one side is true
!NOTthe oppositethe value is false

Analogy — think of a security guard at a door with a checklist. With AND, you must pass every check to get in. With OR, passing any one check is enough. NOT turns a "yes" on the list into a "no".


2. AND — &&

&& gives true only when the left side and the right side are both true:

dart
void main() {
  var hasTicket = true;
  var isOldEnough = true;
  print(hasTicket && isOldEnough);

  isOldEnough = false;
  print(hasTicket && isOldEnough);
}
output
true
false

After isOldEnough changes to false, one check fails — and with AND, one failure makes the whole answer false.


3. The AND Truth Table

A truth table lists every possible combination of inputs and the result. With two bool values there are only four combinations:

aba && b
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse
dart
void main() {
  var yes = true;
  var no = false;
  print(yes && yes);
  print(yes && no);
  print(no && yes);
  print(no && no);
}
output
true
false
false
false

Only one row out of four is true. AND is strict.


4. OR — || and Its Truth Table

|| gives true when at least one side is true. It is written with two vertical bars (on most keyboards, Shift + the backslash key).

aba || b
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse
dart
void main() {
  var yes = true;
  var no = false;
  print(yes || yes);
  print(yes || no);
  print(no || yes);
  print(no || no);
}
output
true
true
true
false

OR is the mirror image of AND: only one row is false — the one where nothing is true.


5. NOT — !

! is written in front of a single bool and flips it: true becomes false, and false becomes true.

dart
void main() {
  var isRaining = false;
  print(!isRaining);
  print(!true);
}
output
true
false

Read !isRaining aloud as "not raining". Because it works on one value, ! is called a unary operator; && and || need two values, one on each side.

This ! goes before a value. The null assertion ! from Lecture 015 goes after a value — same symbol, completely different job.


6. Combining Comparisons

The most common use of logical operators is joining comparisons. Each comparison produces a bool, and && or || joins those bools:

dart
void main() {
  var age = 15;
  var isTeenager = age >= 13 && age <= 19;
  print('Teenager: $isTeenager');

  var day = 7;
  var isWeekend = day == 6 || day == 7;
  print('Weekend: $isWeekend');
}
output
Teenager: true
Weekend: true

Dart works out age >= 13 (true) and age <= 19 (true) first, then joins them with &&. Comparisons always happen before && and ||, so no brackets are needed here.


7. Only bool Values Allowed

Logical operators work on bool values and nothing else. Dart does not treat 1 as "true" or an empty string as "false", as some other languages do:

dart
void main() {
  var marks = 80;
  print(marks && true);
}
output
Error: A value of type 'int' can't be assigned to a variable of type 'bool'.
  print(marks && true);
        ^

If you mean "marks is above zero", say so with a comparison: marks > 0 && true.


8. Mixing &&, ||, and !

You can build longer rules by mixing operators. Dart applies ! first, then &&, then || — but rather than memorize that, use brackets to make your meaning obvious:

dart
void main() {
  var isMember = false;
  var total = 2500;
  var isBlocked = false;
  var freeDelivery = (isMember || total >= 2000) && !isBlocked;
  print('Free delivery: $freeDelivery');
}
output
Free delivery: true

Read it in plain words: "member or big order — and not blocked". Sara is not a member, but her order of 2500 is large enough, and her account is not blocked. We will study the full order of all Dart operators in Lecture 021 — Operator Precedence & Custom Operators.


9. Negating a Whole Condition

Put brackets around a condition and ! in front of it to ask for the opposite of the whole thing:

dart
void main() {
  var temperature = 35;
  var isComfortable = temperature >= 18 && temperature <= 26;
  print(!isComfortable);
  print(!(temperature >= 18 && temperature <= 26));
  print(temperature < 18 || temperature > 26);
}
output
true
true
true

All three lines ask "is it uncomfortable?". Notice the last one: "not (warm enough and cool enough)" is the same as "too cold or too hot". When you flip a combined condition, && becomes || and each comparison flips too. Often the version without ! is easier to read.


10. Short-Circuit Evaluation

Dart evaluates && and || from left to right, and stops as soon as the answer is certain. This is called short-circuit evaluation.

  • With &&, if the left side is false, the answer must be false — the right side is never evaluated.
  • With ||, if the left side is true, the answer must be true — the right side is never evaluated.

This matters when the right side would cause a problem. Dividing by zero with ~/ crashes the program:

dart
void main() {
  var totalMarks = 240;
  var subjects = 0;
  print(totalMarks ~/ subjects > 50);
}
output
Unhandled exception:
IntegerDivisionByZeroException

Put a guard on the left with &&:

dart
void main() {
  var totalMarks = 240;
  var subjects = 0;
  print(subjects != 0 && totalMarks ~/ subjects > 50);
}
output
false

subjects != 0 is false, so Dart stops right there. The division never runs, and nothing crashes. Order matters: the safety check must come first.

Editor hint. If you type the truth-table programs (sections 3 and 4, Examples 1 and 2) into an editor, some right-hand sides may be greyed out with the hint "Dead code". Dart can see that no is always false, so in no && yes the right side can never run. That hint is short-circuiting, reported before the program even starts.


11. Short-Circuit with Nullable Values

Short-circuiting is how you check a nullable value and use it in a single expression. A nullable int? cannot be compared directly:

dart
void main() {
  var age = int.tryParse('abc');
  print(age >= 18);
}
output
Error: Operator '>=' cannot be called on 'int?' because it is potentially null.
  print(age >= 18);
            ^^

Put the null check on the left of &&:

dart
void main() {
  var age = int.tryParse('abc');
  print(age != null && age >= 18);

  var otherAge = int.tryParse('25');
  print(otherAge != null && otherAge >= 18);
}
output
false
true

Dart understands short-circuiting too: the right side only runs when age != null was true, so it allows age >= 18 there. The null-aware operator from Lecture 014 gives another way to write the same rule: (age ?? 0) >= 18 treats a missing age as 0.


12. A Real-World Example

dart
void main() {
  var customer = 'Sara';
  var balance = 5000.0;
  var amount = 1200.0;
  var dailyLimit = 2000.0;
  var pinCorrect = true;
  var isBlocked = false;

  var hasMoney = amount <= balance;
  var withinLimit = amount <= dailyLimit;
  var canWithdraw = pinCorrect && !isBlocked && hasMoney && withinLimit;

  print('Customer: $customer');
  print('Enough money: $hasMoney');
  print('Within limit: $withinLimit');
  print('Can withdraw: $canWithdraw');
}
output
Customer: Sara
Enough money: true
Within limit: true
Can withdraw: true

Each rule is a comparison stored in a well-named bool, and && joins them: correct PIN, not blocked, enough money, within the limit. Change any one input — amount = 2500.0, or isBlocked = true — and canWithdraw becomes false. Making the program act differently based on that answer is the job of if, which arrives in Lecture 022.


Code Examples

Example 1 — The AND Truth Table

dart
void main() {
  var t = true;
  var f = false;
  print('true && true   = ${t && t}');
  print('true && false  = ${t && f}');
  print('false && true  = ${f && t}');
  print('false && false = ${f && f}');
}
output
true && true   = true
true && false  = false
false && true  = false
false && false = false

Example 2 — The OR Truth Table

dart
void main() {
  var t = true;
  var f = false;
  print('true || true   = ${t || t}');
  print('true || false  = ${t || f}');
  print('false || true  = ${f || t}');
  print('false || false = ${f || f}');
}
output
true || true   = true
true || false  = true
false || true  = true
false || false = false

Example 3 — Flipping with NOT

dart
void main() {
  var isOpen = true;
  var isClosed = !isOpen;
  print('Open: $isOpen');
  print('Closed: $isClosed');
  print('Not not open: ${!!isOpen}');
}
output
Open: true
Closed: false
Not not open: true

Example 4 — A Range Check

dart
void main() {
  var age = 21;
  var isTeenager = age >= 13 && age <= 19;
  print('Age $age is a teenager: $isTeenager');
}
output
Age 21 is a teenager: false

Example 5 — One of Several Values

dart
void main() {
  var day = 3;
  var isWeekend = day == 6 || day == 7;
  var isWeekday = !isWeekend;
  print('Weekend: $isWeekend');
  print('Weekday: $isWeekday');
}
output
Weekend: false
Weekday: true

Example 6 — A Short-Circuit Guard

dart
void main() {
  var totalPrice = 900;
  var items = 0;
  var averageIsHigh = items > 0 && totalPrice ~/ items > 100;
  print('Average above 100: $averageIsHigh');
}
output
Average above 100: false

How it works

  1. items > 0 is false.
  2. && already knows the answer is false, so totalPrice ~/ items is never calculated.
  3. Without the guard, dividing by 0 would crash the program.

Example 7 — Student Marks

dart
void main() {
  var student = 'Ali';
  var math = 80;
  var science = 45;
  var english = 85;

  var passedAll = math >= 50 && science >= 50 && english >= 50;
  var passedAny = math >= 50 || science >= 50 || english >= 50;
  var hasDistinction = math >= 80 && english >= 80;

  print('Student: $student');
  print('Passed all: $passedAll');
  print('Passed at least one: $passedAny');
  print('Distinction in math and English: $hasDistinction');
}
output
Student: Ali
Passed all: false
Passed at least one: true
Distinction in math and English: true

How it works

  1. && can join more than two conditions; all must be true. Science (45) fails, so passedAll is false.
  2. || can join more than two as well; one is enough, so passedAny is true.
  3. hasDistinction checks only math and English, and both are 80 or more.

Example 8 — Shopping: Free Delivery

dart
void main() {
  var customer = 'Sara';
  var orderTotal = 1500;
  var points = 1200;
  var isMember = points >= 1000;

  var freeDelivery = isMember || orderTotal >= 2000;
  print('Customer: $customer');
  print('Order total: $orderTotal');
  print('Free delivery: $freeDelivery');
}
output
Customer: Sara
Order total: 1500
Free delivery: true

How it works

  1. Sara counts as a member once she has at least 1000 points; with 1200, isMember is true.
  2. The rule is "member or order of at least 2000", and the left side is already true.
  3. || short-circuits: orderTotal >= 2000 is not even checked.

Example 9 — Bank Balance

dart
void main() {
  var balance = 800.0;
  var amount = 1000.0;
  var hasOverdraft = false;

  var canPay = amount <= balance || hasOverdraft;
  var isLowBalance = balance < 1000;

  print('Can pay: $canPay');
  print('Low balance warning: $isLowBalance');
  print('Account healthy: ${!isLowBalance && canPay}');
}
output
Can pay: false
Low balance warning: true
Account healthy: false

How it works

  1. 1000.0 is more than 800.0 and there is no overdraft, so canPay is false.
  2. !isLowBalance is false, so the last && is false without checking canPay.

Example 10 — Temperature Check

dart
void main() {
  var temperature = 42.5;
  var isComfortable = temperature >= 18 && temperature <= 26;
  var isExtreme = temperature < 0 || temperature > 40;

  print('Temperature: $temperature');
  print('Comfortable: $isComfortable');
  print('Extreme: $isExtreme');
}
output
Temperature: 42.5
Comfortable: false
Extreme: true

How it works

  1. "Comfortable" is a range: at least 18 and at most 26. 42.5 fails the second part.
  2. "Extreme" is outside a range: below 0 or above 40. 42.5 passes the second part.
  3. A range uses &&; outside a range uses ||.

Common Beginner Mistakes

1. Writing a range like in maths

dart
void main() {
  var age = 15;
  // print(13 <= age <= 19); // ✗
}
output
Error: A comparison expression can't be an operand of another comparison expression.
Try putting parentheses around one of the comparisons.

Dart works out 13 <= age first, gets a bool, then tries to compare that bool with 19. Write two comparisons joined by &&: age >= 13 && age <= 19.

2. Leaving out the variable on the right of ||

dart
void main() {
  var day = 6;
  // print(day == 6 || 7); // ✗
}
output
Error: A value of type 'int' can't be assigned to a variable of type 'bool'.

In English we say "day is 6 or 7", but each side of || must be a complete bool. Write day == 6 || day == 7.

3. Using numbers or text as if they were bool

dart
void main() {
  var hasTicket = true;
  // print(hasTicket && 'yes'); // ✗
}
output
Error: A value of type 'String' can't be assigned to a variable of type 'bool'.

Both sides of && and || must be bool. Dart never converts other types for you.

4. Putting ! in the wrong place

dart
void main() {
  var age = 20;
  // print(!age > 18); // ✗
}
output
Error: A value of type 'int' can't be assigned to a variable of type 'bool'.
Error: The operator '>' isn't defined for the type 'bool'.

! applies to age alone, which is an int. Put brackets around the comparison — !(age > 18) — or, better, just write age <= 18.

5. Putting the risky check first

dart
void main() {
  var totalMarks = 240;
  var subjects = 0;
  print(subjects != 0 && totalMarks ~/ subjects > 50); // ✓ guard first
  // print(totalMarks ~/ subjects > 50 && subjects != 0); // ✗ crashes
}
output
false

The ✗ line compiles, but crashes with IntegerDivisionByZeroException while running. The guard only helps when it is on the left.

6. Mixing && and || without brackets

isMember || total >= 2000 && !isBlocked is valid code, but it means isMember || (total >= 2000 && !isBlocked) — a blocked member would still get free delivery. Add brackets that say what you mean.


Best Practices

  • Store each part of a rule in a well-named bool (isTeenager, hasMoney) and join the names — the final line then reads like a sentence.
  • Use brackets whenever you mix && and ||, even if Dart would get it right without them.
  • Put cheap, safe checks — != null, != 0, isNotEmpty — on the left of &&.
  • Prefer a direct comparison over a negated one: age < 18 instead of !(age >= 18).
  • Avoid double negatives such as !isNotReady; name the variable isReady instead.
  • Give bool variables yes/no names starting with is, has, or can.

Mini Challenge

Zayan wants to ride the roller coaster. The rule is: he must be at least 120 cm tall, and either at least 8 years old or riding with an adult. Zayan is 125 cm tall, 6 years old, and riding with his father. Print each part of the rule and the final answer.

Your program should print exactly:

output
Tall enough: true
Old enough: false
Can ride: true

Try it yourself before opening the solution.

Show solution
dart
void main() {
  var height = 125;
  var age = 6;
  var withAdult = true;

  var tallEnough = height >= 120;
  var oldEnough = age >= 8;
  var canRide = tallEnough && (oldEnough || withAdult);

  print('Tall enough: $tallEnough');
  print('Old enough: $oldEnough');
  print('Can ride: $canRide');
}
output
Tall enough: true
Old enough: false
Can ride: true

How it works

  1. tallEnough is true because 125 >= 120.
  2. oldEnough is false, but withAdult is true, so the bracketed || gives true.
  3. true && true gives true. The brackets matter: the rule is "tall and (old or with an adult)".

Practice Questions

Work through these in order — they run from beginner to application to challenge. Every question gives you exact values, so you can compare your output with the answers.

Beginner

  1. Store a = 10 > 5 and b = 10 < 5. Print a && b, a || b, and !a, each on its own line.
  2. Store number = 7. Print whether it is greater than 5 and less than 10.
  3. Without running it, predict the output of print(!(5 > 3));. Then check your answer.

Application

  1. Store day = 7. Print Weekend: true if day is 6 or 7 (use ||).

  2. Store password = 'dart123'. Print whether its length is at least 6 and it is not equal to '123456'.

  3. Rewrite !(age >= 18) so that it uses no ! and gives the same result. Prove it with age = 16, printing both versions.

  4. This program does not compile. Fix it so it prints Pleasant: true:

    dart
    void main() {
      var temperature = 22;
      // print('Pleasant: ${15 <= temperature <= 25}');
    }
  5. Store input = 'abc'. Use int.tryParse and a single && expression to print whether the input is a valid number greater than 0. Then change input to '42'.

Challenge

  1. A year is a leap year if it is divisible by 4 and not by 100 — or if it is divisible by 400. Print whether 2024, 1900, and 2000 are leap years, in the form 2024: true.

  2. This program crashes while running. Fix it without adding any new code — only by reordering — so it prints Starts with S: false:

    dart
    void main() {
      var name = '';
      var startsWithS = name[0] == 'S' && name.isNotEmpty;
      print('Starts with S: $startsWithS');
    }

    Explain in one sentence why the order matters.


Practice Answers

Question 1 — Solution

dart
void main() {
  var a = 10 > 5;
  var b = 10 < 5;
  print(a && b);
  print(a || b);
  print(!a);
}
output
false
true
false

Question 2 — Solution

dart
void main() {
  var number = 7;
  print(number > 5 && number < 10);
}
output
true

Question 3 — Solution

5 > 3 is true, and ! flips it to false.

dart
void main() {
  print(!(5 > 3));
}
output
false

Question 4 — Solution

dart
void main() {
  var day = 7;
  var isWeekend = day == 6 || day == 7;
  print('Weekend: $isWeekend');
}
output
Weekend: true

Question 5 — Solution

dart
void main() {
  var password = 'dart123';
  var isStrong = password.length >= 6 && password != '123456';
  print(isStrong);
}
output
true

Question 6 — Solution

"Not at least 18" means "less than 18".

dart
void main() {
  var age = 16;
  print(!(age >= 18));
  print(age < 18);
}
output
true
true

Question 7 — Solution

Split the range into two comparisons joined by &&:

dart
void main() {
  var temperature = 22;
  print('Pleasant: ${temperature >= 15 && temperature <= 25}');
}
output
Pleasant: true

Question 8 — Solution

dart
void main() {
  var input = 'abc';
  var number = int.tryParse(input);
  print(number != null && number > 0);

  input = '42';
  number = int.tryParse(input);
  print(number != null && number > 0);
}
output
false
true

For 'abc', number is null, so && stops at the left side and never compares null with 0.

Question 9 — Solution

dart
void main() {
  var year = 2024;
  print('$year: ${(year % 4 == 0 && year % 100 != 0) || year % 400 == 0}');
  year = 1900;
  print('$year: ${(year % 4 == 0 && year % 100 != 0) || year % 400 == 0}');
  year = 2000;
  print('$year: ${(year % 4 == 0 && year % 100 != 0) || year % 400 == 0}');
}
output
2024: true
1900: false
2000: true

1900 is divisible by 4 but also by 100, so the bracketed part is false, and it is not divisible by 400. 2000 fails the bracketed part too, but passes year % 400 == 0.

Question 10 — Solution

dart
void main() {
  var name = '';
  var startsWithS = name.isNotEmpty && name[0] == 'S';
  print('Starts with S: $startsWithS');
}
output
Starts with S: false

With name.isNotEmpty first, && stops as soon as it sees false, so name[0] is never read on an empty string — in the original order it was read first and caused a RangeError.


Quick Revision

  1. What does && require to give true?
  2. What does || require to give true?
  3. What does ! do, and why is it called a unary operator?
  4. How many rows does the truth table for two bool values have?
  5. Why is 13 <= age <= 19 an error, and how do you write it instead?
  6. What is short-circuit evaluation?
  7. In subjects != 0 && totalMarks ~/ subjects > 50, why must the check come first?
  8. Which operator does a range check use, and which does an "outside the range" check use?

Key Takeaways

  • Logical operators join bool values into a single bool.
  • && (AND) is true only when both sides are true.
  • || (OR) is true when at least one side is true.
  • ! (NOT) goes in front of one bool and flips it.
  • Both sides of && and || must be real bool values — usually comparisons.
  • A range is x >= low && x <= high; outside a range is x < low || x > high.
  • && and || short-circuit: they stop as soon as the answer is certain, so a safe check on the left protects a risky expression on the right.
  • Use brackets when mixing && and ||, and store parts of a rule in well-named bools.

What's Next?

Lecture 20 — Increment, Conditional & Cascade Operators. We can now compute a true or false answer for almost any rule. Next we meet the operators that make everyday code shorter: ++ and -- for counting up and down, the conditional operator ? : that picks one of two values based on a bool — the first time our answers actually choose something — and the cascade .. for doing several things to the same object.


Video lecture

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