On this page24 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Are Logical Operators?
- 2. AND — &&
- 3. The AND Truth Table
- 4. OR — || and Its Truth Table
- 5. NOT — !
- 6. Combining Comparisons
- 7. Only bool Values Allowed
- 8. Mixing &&, ||, and !
- 9. Negating a Whole Condition
- 10. Short-Circuit Evaluation
- 11. Short-Circuit with Nullable Values
- 12. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 19 — Logical Operators
| Category | 4 — Operators |
| Lecture | 019 of 140 |
| Estimated video length | 10 min |
| Target | Dart 3.12.2 |
| Level | Beginner |
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/falseanswers 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 thebooltype with its two values,trueandfalse- 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:
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 aboolto 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:
| Operator | Name | Means | true when… |
|---|---|---|---|
&& | AND | both | both sides are true |
|| | OR | either | at least one side is true |
! | NOT | the opposite | the 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:
void main() {
var hasTicket = true;
var isOldEnough = true;
print(hasTicket && isOldEnough);
isOldEnough = false;
print(hasTicket && isOldEnough);
}true
falseAfter 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:
a | b | a && b |
|---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
void main() {
var yes = true;
var no = false;
print(yes && yes);
print(yes && no);
print(no && yes);
print(no && no);
}true
false
false
falseOnly 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).
a | b | a || b |
|---|---|---|
true | true | true |
true | false | true |
false | true | true |
false | false | false |
void main() {
var yes = true;
var no = false;
print(yes || yes);
print(yes || no);
print(no || yes);
print(no || no);
}true
true
true
falseOR 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.
void main() {
var isRaining = false;
print(!isRaining);
print(!true);
}true
falseRead !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:
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');
}Teenager: true
Weekend: trueDart 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:
void main() {
var marks = 80;
print(marks && true);
}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:
void main() {
var isMember = false;
var total = 2500;
var isBlocked = false;
var freeDelivery = (isMember || total >= 2000) && !isBlocked;
print('Free delivery: $freeDelivery');
}Free delivery: trueRead 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:
void main() {
var temperature = 35;
var isComfortable = temperature >= 18 && temperature <= 26;
print(!isComfortable);
print(!(temperature >= 18 && temperature <= 26));
print(temperature < 18 || temperature > 26);
}true
true
trueAll 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 isfalse, the answer must befalse— the right side is never evaluated. - With
||, if the left side istrue, the answer must betrue— the right side is never evaluated.
This matters when the right side would cause a problem. Dividing by zero with ~/ crashes
the program:
void main() {
var totalMarks = 240;
var subjects = 0;
print(totalMarks ~/ subjects > 50);
}Unhandled exception:
IntegerDivisionByZeroExceptionPut a guard on the left with &&:
void main() {
var totalMarks = 240;
var subjects = 0;
print(subjects != 0 && totalMarks ~/ subjects > 50);
}falsesubjects != 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
nois alwaysfalse, so inno && yesthe 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:
void main() {
var age = int.tryParse('abc');
print(age >= 18);
}Error: Operator '>=' cannot be called on 'int?' because it is potentially null.
print(age >= 18);
^^Put the null check on the left of &&:
void main() {
var age = int.tryParse('abc');
print(age != null && age >= 18);
var otherAge = int.tryParse('25');
print(otherAge != null && otherAge >= 18);
}false
trueDart 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
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');
}Customer: Sara
Enough money: true
Within limit: true
Can withdraw: trueEach 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
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}');
}true && true = true
true && false = false
false && true = false
false && false = falseExample 2 — The OR Truth Table
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}');
}true || true = true
true || false = true
false || true = true
false || false = falseExample 3 — Flipping with NOT
void main() {
var isOpen = true;
var isClosed = !isOpen;
print('Open: $isOpen');
print('Closed: $isClosed');
print('Not not open: ${!!isOpen}');
}Open: true
Closed: false
Not not open: trueExample 4 — A Range Check
void main() {
var age = 21;
var isTeenager = age >= 13 && age <= 19;
print('Age $age is a teenager: $isTeenager');
}Age 21 is a teenager: falseExample 5 — One of Several Values
void main() {
var day = 3;
var isWeekend = day == 6 || day == 7;
var isWeekday = !isWeekend;
print('Weekend: $isWeekend');
print('Weekday: $isWeekday');
}Weekend: false
Weekday: trueExample 6 — A Short-Circuit Guard
void main() {
var totalPrice = 900;
var items = 0;
var averageIsHigh = items > 0 && totalPrice ~/ items > 100;
print('Average above 100: $averageIsHigh');
}Average above 100: falseHow it works
items > 0isfalse.&&already knows the answer isfalse, sototalPrice ~/ itemsis never calculated.- Without the guard, dividing by
0would crash the program.
Example 7 — Student Marks
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');
}Student: Ali
Passed all: false
Passed at least one: true
Distinction in math and English: trueHow it works
&&can join more than two conditions; all must betrue. Science (45) fails, sopassedAllisfalse.||can join more than two as well; one is enough, sopassedAnyistrue.hasDistinctionchecks only math and English, and both are80or more.
Example 8 — Shopping: Free Delivery
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');
}Customer: Sara
Order total: 1500
Free delivery: trueHow it works
- Sara counts as a member once she has at least
1000points; with1200,isMemberistrue. - The rule is "member or order of at least
2000", and the left side is alreadytrue. ||short-circuits:orderTotal >= 2000is not even checked.
Example 9 — Bank Balance
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}');
}Can pay: false
Low balance warning: true
Account healthy: falseHow it works
1000.0is more than800.0and there is no overdraft, socanPayisfalse.!isLowBalanceisfalse, so the last&&isfalsewithout checkingcanPay.
Example 10 — Temperature Check
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');
}Temperature: 42.5
Comfortable: false
Extreme: trueHow it works
- "Comfortable" is a range: at least
18and at most26.42.5fails the second part. - "Extreme" is outside a range: below
0or above40.42.5passes the second part. - A range uses
&&; outside a range uses||.
Common Beginner Mistakes
1. Writing a range like in maths
void main() {
var age = 15;
// print(13 <= age <= 19); // ✗
}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 ||
void main() {
var day = 6;
// print(day == 6 || 7); // ✗
}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
void main() {
var hasTicket = true;
// print(hasTicket && 'yes'); // ✗
}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
void main() {
var age = 20;
// print(!age > 18); // ✗
}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
void main() {
var totalMarks = 240;
var subjects = 0;
print(subjects != 0 && totalMarks ~/ subjects > 50); // ✓ guard first
// print(totalMarks ~/ subjects > 50 && subjects != 0); // ✗ crashes
}falseThe ✗ 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 < 18instead of!(age >= 18). - Avoid double negatives such as
!isNotReady; name the variableisReadyinstead. - Give
boolvariables yes/no names starting withis,has, orcan.
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:
Tall enough: true
Old enough: false
Can ride: trueTry it yourself before opening the solution.
Show solution
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');
}Tall enough: true
Old enough: false
Can ride: trueHow it works
tallEnoughistruebecause125 >= 120.oldEnoughisfalse, butwithAdultistrue, so the bracketed||givestrue.true && truegivestrue. 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
- Store
a = 10 > 5andb = 10 < 5. Printa && b,a || b, and!a, each on its own line. - Store
number = 7. Print whether it is greater than5and less than10. - Without running it, predict the output of
print(!(5 > 3));. Then check your answer.
Application
-
Store
day = 7. PrintWeekend: trueifdayis6or7(use||). -
Store
password = 'dart123'. Print whether its length is at least6and it is not equal to'123456'. -
Rewrite
!(age >= 18)so that it uses no!and gives the same result. Prove it withage = 16, printing both versions. -
This program does not compile. Fix it so it prints
Pleasant: true:void main() { var temperature = 22; // print('Pleasant: ${15 <= temperature <= 25}'); } -
Store
input = 'abc'. Useint.tryParseand a single&&expression to print whether the input is a valid number greater than0. Then changeinputto'42'.
Challenge
-
A year is a leap year if it is divisible by
4and not by100— or if it is divisible by400. Print whether2024,1900, and2000are leap years, in the form2024: true. -
This program crashes while running. Fix it without adding any new code — only by reordering — so it prints
Starts with S: false: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
void main() {
var a = 10 > 5;
var b = 10 < 5;
print(a && b);
print(a || b);
print(!a);
}false
true
falseQuestion 2 — Solution
void main() {
var number = 7;
print(number > 5 && number < 10);
}trueQuestion 3 — Solution
5 > 3 is true, and ! flips it to false.
void main() {
print(!(5 > 3));
}falseQuestion 4 — Solution
void main() {
var day = 7;
var isWeekend = day == 6 || day == 7;
print('Weekend: $isWeekend');
}Weekend: trueQuestion 5 — Solution
void main() {
var password = 'dart123';
var isStrong = password.length >= 6 && password != '123456';
print(isStrong);
}trueQuestion 6 — Solution
"Not at least 18" means "less than 18".
void main() {
var age = 16;
print(!(age >= 18));
print(age < 18);
}true
trueQuestion 7 — Solution
Split the range into two comparisons joined by &&:
void main() {
var temperature = 22;
print('Pleasant: ${temperature >= 15 && temperature <= 25}');
}Pleasant: trueQuestion 8 — Solution
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);
}false
trueFor 'abc', number is null, so && stops at the left side and never compares null
with 0.
Question 9 — Solution
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}');
}2024: true
1900: false
2000: true1900 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
void main() {
var name = '';
var startsWithS = name.isNotEmpty && name[0] == 'S';
print('Starts with S: $startsWithS');
}Starts with S: falseWith 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
- What does
&&require to givetrue? - What does
||require to givetrue? - What does
!do, and why is it called a unary operator? - How many rows does the truth table for two
boolvalues have? - Why is
13 <= age <= 19an error, and how do you write it instead? - What is short-circuit evaluation?
- In
subjects != 0 && totalMarks ~/ subjects > 50, why must the check come first? - Which operator does a range check use, and which does an "outside the range" check use?
Key Takeaways
- Logical operators join
boolvalues into a singlebool. &&(AND) istrueonly when both sides aretrue.||(OR) istruewhen at least one side istrue.!(NOT) goes in front of onebooland flips it.- Both sides of
&&and||must be realboolvalues — usually comparisons. - A range is
x >= low && x <= high; outside a range isx < 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-namedbools.
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.