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

OperatorMeaningExampleResult
==equal to30 == 30True
!=not equal to"UPI" != "COD"True
<less than22 < 30True
>greater than22 > 30False
<=less than or equal30 <= 30True
>=greater than or equal18 >= 21False

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

OperatorMeaningTrue when
a and bbothboth a and b are true
a or beitherat least one of a, b is true
not aoppositea 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
True

How 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
False

How 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 False

How 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
True

How 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
True

How 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.0

How 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 True

How 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.3

How 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 True

How 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: False

Each 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
False

The 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 18

The 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 True or False, and every comparison (==, !=, <, >, <=, >=) returns one.
  • Chain comparisons like 20 <= minutes <= 30 for ranges.
  • not binds first, then and, then or; use brackets whenever you mix and with or.
  • in and not in test membership and replace long or chains.
  • Use is None for 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.

Newsletter

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