All 57 lessons
Lesson 7 of 57 · Control Flow
Booleans, Comparisons and Logical Operators
By Skillancy Editorial Team
Run the code in the free Python Compiler or on your own computer. Lessons that read files use the FreshBasket practice data: download the dataset (ZIP).
Every filter you will ever write in SQL or pandas is a yes/no question asked of each row: Was the delivery late? Is the customer in Pune and a Prime member? Was the payment made by UPI or card? Python answers such questions with a special type called a boolean, which has exactly two values, True and False.
This lesson teaches you to build those questions. You will learn the comparison operators, how to combine conditions with and, or and not, why precedence and brackets matter, and a few Python-specific tricks like chained comparisons and short-circuit evaluation. These ideas power the if statements in the next lesson and the row filters in pandas later.
Comparison operators
| Operator | Meaning | Example | Result |
|---|---|---|---|
== | equal to | 30 == 30 | True |
!= | not equal to | "UPI" != "COD" | True |
< | less than | 22 < 30 | True |
> | greater than | 22 > 30 | False |
<= | less than or equal | 30 <= 30 | True |
>= | greater than or equal | 18 >= 21 | False |
Each comparison produces a boolean. Remember that a single = assigns a value and a double == compares. Mixing them up is the number one beginner error in this topic.
Logical operators
| Operator | Meaning | True when |
|---|---|---|
a and b | both | both a and b are true |
a or b | either | at least one of a, b is true |
not a | opposite | a is false |
When several appear in one expression, Python evaluates not first, then and, then or. Brackets override that order. Because most people do not remember the order, put brackets around any mix of and and or.
Worked examples
For the first few examples we use a list of real delivery times in minutes taken from the first twelve delivered FreshBasket orders. A list is an ordered collection written in square brackets; the next module covers lists in depth, and for now it is just a convenient container.
Example 1: Basic comparisons
Question: The promise is 30 minutes. For the first order in our sample, was the delivery within the promise?
import pandas as pd
orders = pd.read_csv("orders.csv")
times = orders["delivery_minutes"].dropna().astype(int).head(12).tolist()
print(times)
first = times[0]
promise = 30
print(first, first <= promise)
print(first == promise, first != promise)
print(first > promise)Output
[34, 24, 37, 29, 20, 30, 25, 23, 18, 20, 36, 33]
34 False
False True
TrueHow it works: Pandas loads the file and we pull out twelve delivery times (the pandas lessons explain each step later). Then each comparison yields a boolean: first <= promise asks whether the first delivery met the promise.
Reading the result: The first delivery took 34 minutes, so <= returned False while > returned True. Note that comparing 34 with 30 using == gives False and != gives True.
Example 2: Chained comparisons
Question: Classify a delivery time of 24 minutes: is it between 20 and 30 minutes inclusive?
minutes = 24
print(20 <= minutes <= 30)
print(20 <= minutes and minutes <= 30)
print(0 < minutes < 20)Output
True
True
FalseHow it works: Python lets you chain comparisons exactly like mathematics. 20 <= minutes <= 30 means "20 is at most minutes, and minutes is at most 30". It is shorthand for the longer and version in the second line, and it reads far more naturally.
Reading the result: Both versions give True for 24 minutes, and the last line, asking whether it is below 20, gives False. Chaining is handy for ranges such as age bands and price slabs.
Example 3: Combining conditions with and, or, not
Question: FreshBasket gives free delivery when the basket is at least ₹299, or when the customer is a Prime member. A priority customer is Prime and in Bengaluru. Test three customers.
def describe(basket, is_prime, city):
free_delivery = basket >= 299 or is_prime
priority = is_prime and city == "Bengaluru"
not_prime = not is_prime
return free_delivery, priority, not_prime
print(describe(450, False, "Mumbai"))
print(describe(120, True, "Bengaluru"))
print(describe(120, False, "Bengaluru"))Output
(True, False, True)
(True, True, False)
(False, False, True)How it works: The function (explained in detail in the functions lesson) packs the three questions together and returns three booleans. or is true if either side is true, and needs both, and not flips a value.
Reading the result: The first customer gets free delivery through the basket value only. The second gets free delivery and priority because Prime satisfies both rules. The third customer is neither. Reading each output tuple in the order (free delivery, priority, not prime) confirms it.
Example 4: Precedence and brackets
Question: The rule is "Prime customers, or customers with a basket of at least ₹500, get a gift, but only in Delhi". Why can a missing bracket give the wrong answer?
is_prime, basket, city = True, 100, "Mumbai"
without_brackets = is_prime or basket >= 500 and city == "Delhi"
with_brackets = (is_prime or basket >= 500) and city == "Delhi"
print(without_brackets, with_brackets)Output
True FalseHow it works: Without brackets, and is evaluated before or, so Python reads the first rule as is_prime or (basket >= 500 and city == "Delhi"). That means any Prime customer in any city would get a gift. The bracketed version applies the city rule to both groups.
Reading the result: The two lines disagree: True against False. The same customer, a Prime member in Mumbai, either gets or does not get a gift depending purely on bracket placement. When rules mix and with or, always bracket explicitly.
Example 5: Comparing strings
Question: Are text comparisons case-sensitive, and how does Python order words?
print("UPI" == "upi")
print("UPI".lower() == "upi")
print("Delhi" < "Mumbai", "apple" < "Banana", "apple" < "banana")
print("Delhi" == "Delhi ")
print("Delhi" == "Delhi ".strip())Output
False
True
True False True
False
TrueHow it works: Strings are equal only when every character matches, including capitals and spaces. Order comparisons (<, >) use alphabetical order based on character codes, where all capital letters come before all lowercase letters. That is why "apple" < "Banana" is false but "apple" < "banana" is true.
Reading the result: The first comparison is False because of capitalisation, and normalising with lower() fixes it. A trailing space also breaks equality until you strip(). Whenever text values will not match, suspect case and whitespace first.
Example 6: Membership with in and not in
Question: Which payment methods count as digital?
digital = ["UPI", "Card", "Wallet"]
print("UPI" in digital)
print("COD" in digital)
print("COD" not in digital)
print("Wal" in "Wallet")Output
True
False
True
TrueHow it works: value in collection is true when the value appears in the collection. It works with lists, strings (as a substring test) and other containers. not in is its opposite. This replaces long chains such as method == "UPI" or method == "Card" or method == "Wallet".
Reading the result: UPI is digital, COD is not, and the string test shows that "Wal" appears inside "Wallet". Use in whenever you would otherwise write a long or chain, just like SQL's IN (...).
Example 7: Short-circuit evaluation
Question: Compute the average basket only when there are orders, without risking division by zero.
orders_count = 0
revenue = 0
is_healthy = orders_count > 0 and revenue / orders_count > 100
print(is_healthy)
orders_count, revenue = 8, 1200
is_healthy = orders_count > 0 and revenue / orders_count > 100
print(is_healthy, revenue / orders_count)Output
False
True 150.0How it works: and stops as soon as it finds a false part, and or stops as soon as it finds a true part. This is called short-circuit evaluation. In the first case orders_count > 0 is false, so Python never evaluates the division, which would have crashed with ZeroDivisionError.
Reading the result: The first run printed False safely. With 8 orders and revenue 1200, the second run evaluates the division and finds an average of 150.0, which is above 100, so the result is True. Always put the guard condition on the left.
Example 8: == versus is
Question: When should I use is instead of ==?
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b)
print(a is b)
print(a is c)
status = None
print(status is None, status == None)Output
True
False
True
True TrueHow it works: == asks "do these have the same value?", while is asks "are these the very same object in memory?". Two separate lists with identical contents are equal but not identical. c = a makes a second name for the same list, so a is c is true.
Reading the result: The output is True, False, True. Use is only for None (and occasionally True/False); use == for everything else. is None is the idiomatic test for a missing value.
Example 9: Counting matches by adding booleans
Question: Of our twelve sample deliveries, how many missed the 30-minute promise, and what percentage is that?
late_flags = [t > 30 for t in times]
print(late_flags)
late_count = sum(late_flags)
print(late_count, round(late_count / len(times) * 100, 1))Output
[True, False, True, False, False, False, False, False, False, False, True, True]
4 33.3How it works: The first line is a list comprehension (explained in a later lesson): read it as "for each time t, record whether t > 30". Since True counts as 1 and False as 0, sum over a list of booleans counts the true ones. This is exactly how pandas computes counts and percentages from filters.
Reading the result: 4 of 12 deliveries were late, which is 33.3%. You will use the "mean of a boolean column equals the percentage" trick all the time in pandas.
Example 10: any() and all()
Question: Was at least one delivery late? Were all deliveries under 45 minutes?
print(any(t > 30 for t in times))
print(all(t < 45 for t in times))
print(any([]), all([]))Output
True
True
False TrueHow it works: any(items) is true if at least one item is true, and all(items) is true only if every item is true. Both stop early once the answer is known. For an empty collection, any is False and all is True, which surprises many people but is logically consistent ("nothing is violating the rule").
Reading the result: Some deliveries were late, so any is True, and every delivery was below 45 minutes, so all is True.
Common mistakes
1. Writing = instead of == in a condition.
x = 5
if x = 5:
print("five")Output
File "example.py", line 2
if x = 5:
^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?2. Writing x == 1 or 2. This does not mean "x equals 1 or 2". Python reads it as (x == 1) or 2, and 2 is always truthy. Write x == 1 or x == 2, or better, x in (1, 2).
3. Forgetting brackets when mixing and with or. Example 4 showed how this changes the meaning.
4. Comparing floats with ==. 0.1 + 0.2 == 0.3 is False. Use round() or math.isclose().
5. Using is to compare values. is may appear to work for small numbers and then fail on larger ones. Use == for values.
Practice questions
Practice 1 (Easy): On-time check
A delivery took 28 minutes. Print whether it was on time (30 minutes or less) and whether it was "express" (under 20 minutes).
Show solution
minutes = 28
print("On time:", minutes <= 30)
print("Express:", minutes < 20)Output
On time: True
Express: FalseEach comparison returns a boolean that print displays directly. 28 meets the promise but is not express.
Practice 2 (Medium): Eligibility rule
A customer qualifies for a loyalty gift if they placed at least 5 orders and (are Prime or spent over ₹3,000). Test a customer with 6 orders, not Prime, who spent ₹3,400, and then one with 4 orders, Prime, who spent ₹5,000.
Show solution
def qualifies(order_count, is_prime, spend):
return order_count >= 5 and (is_prime or spend > 3000)
print(qualifies(6, False, 3400))
print(qualifies(4, True, 5000))Output
True
FalseThe brackets make the Prime/spend choice apply as a unit, and the order count rule must hold in all cases. The first customer qualifies; the second fails the order-count test.
Practice 3 (Hard): Count and verify
Using the times list, count deliveries that were both fast (20 minutes or less) and not equal to exactly 15 minutes, and check using any whether the quickest delivery is under 15.
Show solution
fast_not_15 = sum(t <= 20 and t != 15 for t in times)
quickest_under_15 = any(t < 15 for t in times)
print(fast_not_15, quickest_under_15, min(times))Output
3 False 18The generator expression feeds one boolean per delivery into sum, which counts the true ones. min(times) confirms the shortest delivery is 18 minutes, so the any result of False is consistent.
Key takeaways
- A boolean is
TrueorFalse, and every comparison (==,!=,<,>,<=,>=) returns one. - Chain comparisons like
20 <= minutes <= 30for ranges. notbinds first, thenand, thenor; use brackets whenever you mixandwithor.inandnot intest membership and replace longorchains.- Use
is Nonefor missing values and==for everything else. - Booleans count as 0 and 1, so
sum()of conditions counts matches, which is the basis of percentage calculations in pandas.
Frequently asked questions
What is the difference between and and &?
and, or and not work on single true/false values. In pandas you must use &, | and ~ to combine whole columns of booleans, with brackets around each condition. Using and on a pandas column raises an error, and the pandas lessons explain why.
Why does bool count as a number?
In Python True equals 1 and False equals 0 (bool is a subtype of int). That is what lets sum() count how many conditions are true.
Does or always return True or False?
Not exactly: a or b returns the first truthy operand, and a and b returns the first falsy operand (or the last one). For example "" or "Unknown" evaluates to "Unknown". This is handy for default values.
How do I check the opposite of a long condition?
Wrap it in not (...), or use De Morgan's laws: not (a and b) equals (not a) or (not b). Choose whichever version reads more clearly.
Can I compare different types such as 5 == "5"?
You can, and the result is False because a number and a string are never equal. Ordering comparisons like 5 < "7" raise a TypeError. Convert first.
What does != do with None?
x != None works but x is not None is the recommended form because it is clearer and cannot be fooled by objects that define their own equality.
Keep learning Python
One email a week with Python and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.
Ready to practise? Try the Python interview questions