# Logical and Relational Operators

> Chapter 6 of the Courseiva PYTHON-PCEP curriculum — https://courseiva.com/learn/python-pcep/logical-and-relational-operators

**Official objective:** 3.1 — Use comparison and logical operators to build conditions.

## Introduction

Logical and relational operators let your Python code make decisions based on comparisons. They are the building blocks for conditions in if statements and loops, which you will see on the PCEP-30-02 exam. Without them, your programs could never react differently to different situations.

## The Airport Security Checkpoint Analogy

Before you are allowed to board a flight, a series of conditions must all be met. First, you present your boarding pass and ID at the document check, which leads to a decision: are both your ID and boarding pass valid? This is a logical 'and' condition — both the ID check must pass and the boarding pass check must pass for you to proceed. If either fails, you are stopped.

Next, you walk through the metal detector. The alarm is triggered if you have metal on you or if you have prohibited items in your bag. This is a logical 'or' condition: if the metal detector beeps or the X-ray machine flags your bag, you get pulled aside for a secondary search.

The final gate agent checks your passport number against a list. Is your passport number equal to the number on the manifest? If it is not, you cannot board. This is a relational operator — comparing two values (your passport number and the manifest number) to see if they are equal. The whole process uses relational operators (is your ID expiry date later than today?) and logical operators (combining multiple checks) to decide one thing: whether you get on the plane. Just like in Python, the airport's decision tree is built from simple yes/no conditions combined with 'and', 'or', and 'not'.

## Core explanation

Logical and relational operators are tools that let your Python code ask true/false questions about data. A relational operator compares two values and gives you back either True or False. The most common ones are: == (equal to), != (not equal to), < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to). For example, the expression 5 < 3 asks 'is 5 less than 3?' and produces the answer False. The expression 'hello' == 'hello' asks 'is the string hello equal to the string hello?' and produces True.

Logical operators combine multiple true/false values. In Python, the three logical operators are 'and', 'or', and 'not'. The 'and' operator gives you True only if both conditions on its left and right are True. The 'or' operator gives you True if at least one of the conditions is True. The 'not' operator flips a True to False and a False to True. For example, (5 > 3) and (10 < 20) asks 'is 5 greater than 3 and is 10 less than 20?' Both are True, so the whole expression is True. If you wrote (5 > 3) or (10 > 20), that gives True because the first part is True even though the second is False. The expression not (5 > 3) becomes False because (5 > 3) is True.

These operators replace the need to list every possible outcome manually. Instead of writing out 'if the user is an admin, then show the settings; if the user is not an admin, then hide the settings', you use a relational comparison (user_role == 'admin') and let Python evaluate it.

Why do they exist? Because real programs must react to different inputs. A calculator app uses relational operators to check which button was pressed. A login system uses relational operators (compare the entered password to the stored password) and logical operators (check that the username is correct and the account is not locked).

For PCEP-30-02, you must memorise the exact symbols and their precedence. Precedence means the order in which Python evaluates operators when they appear together. The highest precedence goes to 'not', then relational operators, then 'and', then 'or'. So in the expression not 5 > 3 or 8 == 8 and 2 < 1, Python first evaluates not (5 > 3) which becomes False, then (8 == 8) which is True, then (2 < 1) which is False, then True and False gives False, then False or False gives False.

You also need to know that Python treats truthy and falsy values. Any non-zero number, non-empty string, or non-empty list is treated as True in a logical context. The number 0, the empty string '', the empty list [], and None are treated as False. This matters for short-circuit evaluation: in an 'and' chain, if the first condition is False, Python stops evaluating because the whole thing cannot be True. In an 'or' chain, if the first condition is True, Python stops because the whole thing is already True.

The typical exam question shows a mixed expression with relational and logical operators and asks you to predict the result (True or False). They love to test the difference between = (assignment) and == (equality). Another favourite is asking what happens when you compare incompatible types, like '5' == 5 (string versus integer) which is False. They also test operator chaining: 3 < 5 < 7 is a valid Python expression meaning (3 < 5) and (5 < 7), and it evaluates to True.

## Real-world context

An IT professional, say a junior developer, uses logical and relational operators daily. Consider a typical ticket system for a small company. The developer is asked to write a script that automatically assigns priority to incoming support tickets. The tickets have fields: customer_plan (free or premium), hours_since_first_contact (integer), and has_attached_logs (True or False). The business rule says: 'A ticket gets high priority if the customer is on a premium plan and the ticket is older than 48 hours, or if the ticket has attached logs that indicate a system crash.' The developer writes a condition like:

if (customer_plan == 'premium' and hours_since_first_contact > 48) or (has_attached_logs and log_content_mentions_crash):
    priority = 'high'

This uses relational operators (==, >) and logical operators (and, or). The developer must be careful with parentheses: without them, Python might group the conditions differently because 'and' binds tighter than 'or'. That would change the logic.

Another example: the developer writes a function that validates user input. The user must enter an age between 18 and 65 inclusive. The condition is:

if age >= 18 and age <= 65:
    print('Valid age')

They could also use Python's chaining: if 18 <= age <= 65. Both work. But the developer must check edge cases — what if the user enters a string? The relational operator >= would raise a TypeError because you cannot compare a string to an integer. So the developer first checks the type using type(age) == int.

In testing environments, developers write unit tests that use relational operators to check expected outcomes. If a function is supposed to return the sum of two numbers, the test uses an if statement with == to compare the actual result to the expected result.

- When debugging, developers add temporary print statements that show the value of boolean expressions. For example: print(is_logged_in and has_permission == 'admin')
- When configuring access control, they use logical operators to combine role checks: if role == 'admin' or role == 'manager': allow access
- When processing data pipelines, they use relational operators to filter rows: if value > threshold: flag row

The key skill for an IT professional is to translate business rules into correct boolean logic. A common real-world mistake is missing the difference between 'and' and 'or' — for example, a condition that should be 'customer is premium or customer has priority support add-on' being written with 'and' instead, which locks out customers who have only one of the two.

## Exam focus

The PCEP-30-02 exam tests objective 3.1: 'Use comparison and logical operators to build conditions.' This objective appears in multiple-choice and short-answer questions. The exam loves to check whether you know the exact symbols, operator precedence, short-circuit evaluation, and truthy/falsy behaviour.

- Question type 1: 'What is the value of the following expression?' They give you something like 5 == 5.0 or 3 < 2 and 4 > 1. You must compute the result. The trap: some beginners forget that 5 and 5.0 are equal in value even though they are different types (int and float). The answer is True.
- Question type 2: 'Which of the following expressions evaluates to True?' with four options mixing logical and relational operators. They include one option with wrong parentheses to test if you know that and binds tighter than or.
- Question type 3: 'What does the following code print?' They give a short script with an if-elif-else and ask you to trace the logic.
- Question type 4: 'What is the result of the expression: not True and False?' The answer is False. Many beginners mistakenly think 'not' applies to the whole expression, but it binds only to True, so not True becomes False, then False and False is False.
- Question type 5: 'Which operator is used to check if two values are not equal?' The answer is !=. They might also ask about the difference between = and ==.

Traps they set:
- Chaining syntax: 3 < 5 < 7 is valid, but they might write 3 < 5 and 5 < 7 separately and ask you to recognise it is equivalent.
- Short-circuit evaluation: In the expression False and some_function(), some_function() never executes. They might ask 'what is the output of this code' where a print statement inside the function would not be executed.
- Truthy values: They test that 0, '', [], None are False, and any non-zero number, non-empty string, or non-empty list is True. A classic trap: if []: print('yes') — nothing is printed because [] is falsy.
- Operator precedence: A multi-operator expression without parentheses. You must memorise that not > relational > and > or.

Concepts to memorise:
- The six relational operators: ==, !=, <, >, <=, >=
- The three logical operators: and, or, not
- Python's truthiness rules for non-boolean values
- Short-circuit behaviour
- The fact that comparing different numeric types (int vs float) works, but comparing strings to numbers gives True for == only if the string cannot be converted (e.g., '5' == 5 is False)
- How chaining works: x < y < z is equivalent to x < y and y < z

## Step by step

1. **Identify the values you need to compare** — Look at your problem and pick out two pieces of data that you want to compare. For example, if you are checking if a user is old enough, the two values are the user's age and the minimum age (like 18). Write them down as variables or literals.
2. **Choose the correct relational operator** — Decide which relationship you need: equality (==), inequality (!=), less than (<), greater than (>), less than or equal (<=), or greater than or equal (>=). For the age example, you would use >= because you want to check if the user is 18 or older.
3. **Write the relational expression** — Combine the two values with the operator: user_age >= 18. This expression will be evaluated by Python as either True or False. Remember that strings compared with numbers always give False, so make sure both values are the same type.
4. **Combine multiple conditions with logical operators** — If you have more than one condition, use and, or, or not to join them. For example, to check that a user is both over 18 and has an active subscription, write: user_age >= 18 and subscription_status == 'active'. Use parentheses to control the order of evaluation: (condition1 or condition2) and condition3.
5. **Evaluate the final boolean value** — Python follows precedence rules (not first, then relational operators, then and, then or) and short-circuit rules to compute the final True or False. If you are unsure, add parentheses to make the order explicit. The result is used by an if statement to decide which block of code to run.
6. **Test edge cases** — Check what happens with boundary values (like exactly 18 for the age check), with falsy values (0, empty string, None), and with unexpected types. For example, test user_age = 0 to ensure the condition works correctly (it should be False).

## Comparisons

### = (Assignment) vs == (Equality)

**= (Assignment):**
- Used to assign a value to a variable, e.g., x = 5
- Does not return a value that can be used in an if condition
- Using = inside a condition causes SyntaxError

**== (Equality):**
- Used to compare two values for equality, e.g., x == 5
- Returns a boolean True or False
- Allowed inside if conditions, e.g., if x == 5:

### and (Logical) vs or (Logical)

**and (Logical):**
- Returns True only if both operands are truthy
- Short-circuits on the first falsy operand
- Binds tighter than or in precedence

**or (Logical):**
- Returns True if at least one operand is truthy
- Short-circuits on the first truthy operand
- Binds looser than and in precedence

### is (Identity) vs == (Equality)

**is (Identity):**
- Checks if two variables refer to the same object in memory
- Returns True only for identical objects
- Usually used with None: variable is None

**== (Equality):**
- Checks if two values are equal in value
- Returns True for equivalent values even if different objects
- Used for comparing numbers, strings, and other data

### Truthy Values vs Falsy Values

**Truthy Values:**
- Any non-zero number (e.g., 1, -5, 3.14)
- Any non-empty sequence (e.g., 'hello', [1, 2])
- The boolean True itself

**Falsy Values:**
- The number 0 and 0.0
- Empty sequences: '', [], (), {}
- The boolean False and the None value

### Operator Chaining (3 < 5 < 7) vs Explicit and (3 < 5 and 5 < 7)

**Operator Chaining (3 < 5 < 7):**
- Python syntax: a < b < c
- Each operand is evaluated only once
- Equivalent to (a < b) and (b < c)

**Explicit and (3 < 5 and 5 < 7):**
- Requires writing two comparisons
- The middle operand (b) is evaluated twice
- Gives the same result but is more verbose

## Diagram

_Flowchart showing how relational operators produce a single boolean, then logical operators combine them into a final true/false result._

```mermaid
flowchart TD
    A[Start: Values to Compare] --> B[Relational Operator?]
    B --> |== != < > <= >=| C[Single True/False]
    C --> D{Logical Operator?}
    D --> |and| E[Both True? Return True else False]
    D --> |or| F[At least one True? Return True else False]
    D --> |not| G[Flip True to False, False to True]
    E --> H[Final Boolean Result]
    F --> H
    G --> H
```

## Common misconceptions

- **Misconception:** The = operator is used to check if two values are equal. **Reality:** In Python, = is the assignment operator used to assign a value to a variable. The equality operator is == (two equals signs). Using = inside a condition causes a syntax error because Python does not allow assignment inside expressions like if. (In mathematics, a single equal sign means equality, so beginners naturally transfer that habit to programming. Other languages like JavaScript allow = inside conditions (though it is usually a bug), but Python explicitly disallows it, which confuses newcomers.)
- **Misconception:** The expression 3 < 5 < 7 is invalid and will cause an error. **Reality:** Python supports operator chaining. 3 < 5 < 7 is perfectly valid and is evaluated as (3 < 5) and (5 < 7), which is True. (Most other programming languages do not support chaining, so beginners coming from C++, Java, or JavaScript assume Python also forbids it. The Python behaviour is unique and is often not taught in introductory tutorials.)
- **Misconception:** The 'and' operator returns a boolean value only (True or False). **Reality:** In Python, 'and' returns the first falsy value if any operand is falsy, or the last operand if all are truthy. For example, 0 and 5 returns 0 (not False). Similarly, 'or' returns the first truthy value or the last operand if all are falsy. This is because Python evaluates operands lazily and returns the actual value, not just True or False. (Many beginners learn that logical operators return booleans because that is true in most other languages. Python's behaviour is an optimisation that also allows concise idioms like x = user_input or 'default'. Beginners who expect only True/False are confused when they see a non-boolean value returned.)
- **Misconception:** The 'not' operator always changes True to False and False to True, so not 5 gives False. **Reality:** The 'not' operator first converts its operand to a boolean using truthiness rules. 5 is truthy, so not 5 returns False (the boolean). However, 'not' does not return a value of the original type; it always returns a boolean. So not 'hello' returns False, not the string 'hello'. (Because 'not' is a unary operator that sounds like it should negate the value, beginners think it flips truthiness but keeps the type. They might expect not 'hello' to return '' (empty string) because that is the opposite truthy value. But Python consistently returns a boolean from 'not'.)
- **Misconception:** Comparing a string to a number like '5' == 5 returns True because they look similar. **Reality:** In Python, comparing a string to an integer with == always returns False because they are different types. The exception is numeric types: int and float can compare, but string vs number is always False. (In weakly typed languages like JavaScript, '5' == 5 returns True because the string is coerced to a number. Python is strongly typed and does not perform implicit type conversion in comparisons, so beginners who have used other languages make this assumption.)
- **Misconception:** The 'in' operator is a relational operator. **Reality:** The 'in' operator is a membership operator, not a relational operator. Relational operators (==, !=, <, >, <=, >=) compare values directly. The 'in' operator checks whether a value is present in a sequence (like a string, list, or tuple). For example, 'a' in 'apple' is True, but 'a' == 'apple' is False. (Both 'in' and relational operators return booleans and are often used in conditions, so beginners group them together. The exam expects you to know that 'in' is not part of the relational operator set defined in the objective.)

## Key takeaways

- Relational operators (==, !=, <, >, <=, >=) compare two values and return a boolean True or False.
- Logical operators (and, or, not) combine or invert boolean values to form complex conditions.
- In Python, operator precedence from highest to lowest is: not, then relational operators, then and, then or.
- The expression 3 < 5 < 7 uses chaining and is equivalent to (3 < 5) and (5 < 7).
- Short-circuit evaluation means that in an 'and' chain, evaluation stops at the first False; in an 'or' chain, it stops at the first True.
- The 'not' operator always returns a boolean value, even if its operand is a non-boolean like an integer or string.

## FAQ

**Why does 5 == '5' return False in Python?**

Because Python is strongly typed and does not automatically convert between strings and integers when comparing. The integer 5 and the string '5' are different types, so == always returns False.

**What is the difference between = and == in Python?**

The = operator is used for assignment, like x = 5 (put the value 5 into the variable x). The == operator is used for equality comparison, like x == 5 (ask: is x equal to 5?). Using = in a condition like if x = 5: causes a SyntaxError.

**What does the expression 3 < 5 < 7 mean in Python?**

It is operator chaining and is equivalent to (3 < 5) and (5 < 7). Python checks that 3 is less than 5 and 5 is less than 7. The result is True because both comparisons are true.

**Does Python evaluate every condition in an 'and' or 'or' expression?**

No, Python uses short-circuit evaluation. In an 'and' expression, if the left side is False, Python does not evaluate the right side. In an 'or' expression, if the left side is True, Python does not evaluate the right side. This can be used to avoid errors, like checking if a variable is not None before using it.

**What does the 'not' operator do to a non-boolean value like the number 5?**

The 'not' operator first converts its operand to a boolean using Python's truthiness rules. Since 5 is truthy (not zero), not 5 becomes False. The 'not' operator always returns a boolean (True or False), not the original type.

**Can I compare two different types like a float and an integer?**

Yes, Python allows comparison between numeric types (int and float). For example, 5 == 5.0 returns True because the values are numerically equal. However, comparing a string to a number always returns False.

**What is operator precedence for logical and relational operators?**

From highest to lowest: not (highest), then relational operators (==, !=, <, >, <=, >=), then and, then or (lowest). If you are unsure, use parentheses to group conditions explicitly.

## Check your understanding

1. **What is the result of the Python expression: 10 == 10.0?**

   Answer: True. Python compares numeric values by their mathematical equality, so the integer 10 and the float 10.0 are considered equal.

2. **What does the expression not (5 > 3) evaluate to?**

   Answer: False. Since 5 > 3 is True, applying not flips it to False.

3. **What is the value of the expression: 3 < 5 and 8 == 9?**

   Answer: False. The first part (3 < 5) is True, but the second part (8 == 9) is False. With the 'and' operator, both must be True for the whole expression to be True.

4. **Which Python operator would you use to check if a variable x is not equal to 10?**

   Answer: The != operator, used as x != 10. This returns True if x is not 10, and False otherwise.

5. **What is the output of the following code? print(0 and 5) ?**

   Answer: 0. In Python, the 'and' operator returns the first falsy value if any, so 0 (which is falsy) is returned, not True or False.

---

Interactive version with quiz and diagrams: https://courseiva.com/learn/python-pcep/logical-and-relational-operators
