Module 3.1

Conditional Statements

Learn how to make decisions in your Python programs using if, elif, and else statements. Master comparison operators, boolean logic, and control the flow of your code based on conditions.

55 min read
Beginner
Hands-on
What You Will Learn
  • Write if, elif, and else statements
  • Use comparison and logical operators
  • Create nested conditional structures
  • Master ternary (inline) conditionals
  • Use match-case for pattern matching
Contents
01

Introduction to Conditionals

Conditional statements are the foundation of decision-making in programming. They allow your program to execute different code blocks based on whether certain conditions are true or false. Think of conditionals as the "brain" of your program - they let it react differently to different situations, just like how you make decisions in real life.

Every meaningful program you have ever used relies heavily on conditionals. When you log into a website, conditionals check if your password is correct. When you play a video game, conditionals determine if you won or lost. When you use a calculator app, conditionals handle different operations based on which button you press. Without conditionals, programs would be completely linear - doing the exact same thing every single time, regardless of input or circumstances.

In this comprehensive lesson, we will explore every aspect of conditional statements in Python, starting from the most basic concepts and building up to advanced pattern matching. By the end, you will be able to write programs that make intelligent decisions, validate user input, control access to features, and handle complex business logic with confidence.

Key Concept

What are Conditional Statements?

Conditional statements are programming constructs that let your program make decisions. They work like a fork in the road - based on a condition (a yes/no question), your program chooses which path to take. If the condition is True (yes), one block of code runs. If it is False (no), a different block runs or the code is skipped entirely.

In simple terms: Conditionals let your program ask questions and act differently based on the answers. Without them, programs would do the exact same thing every single time they run, which would be pretty useless!

Real-World Analogy: Think of a traffic light. If the light is green, you go. If it is yellow, you slow down. If it is red, you stop. Your brain makes these decisions automatically based on the condition (light color). Conditionals work the same way in code - they check a condition and decide what to do next!

Another Way to Think About It: Imagine you are writing instructions for a robot. "Go to the kitchen. If there are dirty dishes, wash them. Otherwise, make coffee." That "if" and "otherwise" is exactly what conditionals do in programming!

Why Do We Need Conditionals?

Imagine if every program did the exact same thing every time you ran it. Your calculator would always show "5" no matter what numbers you entered. Your video game character would always walk right, even when you pressed left. That would be pretty useless, right?

Conditionals give programs the ability to think and react. They let your program respond differently based on what is happening - what the user typed, what data was received, what time it is, or any other condition you can imagine. This is what makes software actually useful!

Consider a simple example: an ATM machine. When you insert your card, the machine does not just dispense money immediately. It first checks if your card is valid, then verifies your PIN, then confirms you have sufficient funds, and only then does it dispense the requested amount. Each of these checks is a conditional - a decision point where the program chooses what to do next based on the current situation.

Here are some common scenarios where conditionals are absolutely essential:

User Input Validation

Check if user input meets requirements before processing - like verifying age for restricted content

Access Control

Determine user permissions - admins see different options than regular users

Business Logic

Apply different rules - tax calculations, discounts, pricing tiers based on conditions

Boolean Values: The Foundation

Before we dive into writing conditionals, we need to understand boolean values. Do not worry - this is simpler than it sounds!

A boolean is just a fancy word for something that can only be one of two things: True or False. That is it! Think of it like a light switch - it is either ON (True) or OFF (False). There is no "maybe" or "sort of" - just yes or no, true or false.

Every condition you write in Python will ultimately become either True or False. When you ask Python "Is 5 greater than 3?", it answers True. When you ask "Is 10 equal to 20?", it answers False. These True/False answers are what conditionals use to decide which code to run.

The boolean data type is named after George Boole, a 19th-century mathematician who developed Boolean algebra - the mathematical foundation of digital logic. In Python, boolean values are capitalized (True and False), unlike some other languages that use lowercase. This is important to remember because true (lowercase) will cause an error in Python!

Boolean values can come from three main sources:

  • Direct assignment: You can simply write is_active = True or is_logged_in = False
  • Comparison operations: Expressions like age > 18 or name == "Alice" produce True or False
  • Function returns: Some functions return boolean values, like "hello".startswith("h") returns True

Let us see some examples:

# Boolean values in Python
is_raining = True
is_sunny = False

# Conditions evaluate to boolean values
age = 20
print(age >= 18)  # True (age is greater than or equal to 18)
print(age == 25)  # False (age is not equal to 25)

# Variables can store condition results
is_adult = age >= 18
print(is_adult)  # True
print(type(is_adult))  # <class 'bool'>

Let us break down what is happening in this code:

  • Lines 1-2: We directly assign boolean values to variables. This is useful for flags and settings.
  • Lines 5-6: Comparison expressions produce boolean results. The expression age >= 18 asks "is 20 greater than or equal to 18?" and Python answers True.
  • Lines 8-10: We store the result of a comparison in a variable. This is the "explaining variable" technique - instead of writing the comparison everywhere, we give it a meaningful name.

In the code above, we see that comparison expressions like age >= 18 produce boolean values. The result True or False can be stored in variables and used later in conditional statements. This is a powerful technique for making your code more readable.

Storing boolean results in descriptively named variables is a best practice called "explaining variables" or "self-documenting code." Instead of writing if age >= 18 and has_id and not is_banned:, you could write:

# Using explaining variables for clarity
is_adult = age >= 18
has_valid_id = has_id
is_allowed = not is_banned

if is_adult and has_valid_id and is_allowed:
    print("Access granted")

This makes your code much easier to read and debug, especially when conditions become complex.

Truthy and Falsy Values

Here is something that might surprise you: in Python, everything can be treated as True or False, not just actual boolean values! This is one of Python's most useful (and sometimes confusing) features for beginners.

Think of it this way: Python considers some values to be "empty" or "nothing" - these are called falsy because they act like False in a condition. Everything else is considered to have "something" in it - these are called truthy because they act like True.

The simple rule: Empty things and zero are falsy. Everything else is truthy.

This concept might seem strange at first - how can a number or a string be "true" or "false"? The idea is that Python associates emptiness, nothingness, and zero with False, while anything with content or value is associated with True. This design choice makes conditionals more intuitive and allows for cleaner code.

Here is the complete list of falsy values in Python - memorize these, as everything else is truthy:

  • False - the boolean False itself
  • None - Python's null/nothing value
  • 0 - zero (integer)
  • 0.0 - zero (float)
  • 0j - zero (complex number)
  • "" - empty string
  • [] - empty list
  • () - empty tuple
  • {} - empty dictionary
  • set() - empty set
  • range(0) - empty range
# Falsy values in Python (evaluate to False)
print(bool(False))   # False - the boolean False itself
print(bool(0))       # False - zero (int)
print(bool(0.0))     # False - zero (float)
print(bool(""))      # False - empty string
print(bool([]))      # False - empty list
print(bool({}))      # False - empty dictionary
print(bool(None))    # False - None value

# Truthy values (evaluate to True)
print(bool(True))    # True - the boolean True
print(bool(1))       # True - non-zero number
print(bool(-5))      # True - negative numbers are truthy!
print(bool("Hello")) # True - non-empty string
print(bool([1, 2]))  # True - non-empty list
print(bool(" "))     # True - string with space (not empty!)

This concept is incredibly useful. For example, you can check if a list has items by simply using if my_list: instead of if len(my_list) > 0:. The empty list evaluates to False, so the condition fails when the list is empty.

Here are some practical examples of using truthy/falsy values to write cleaner code:

# Instead of this verbose check:
if len(user_input) > 0:
    process(user_input)

# Write this (more Pythonic):
if user_input:
    process(user_input)

# Instead of checking for None explicitly:
if result != None:
    print(result)

# Write this (but use 'is' for None checks):
if result is not None:
    print(result)

# Checking if a dictionary has entries:
if settings:  # True if settings has any key-value pairs
    apply_settings(settings)
Pro Tip: Use truthy/falsy values to write cleaner code. Instead of if name != "":, you can simply write if name:. This is more Pythonic and easier to read!

Practice: Boolean Basics

Task: Without running the code, predict what each expression will evaluate to (True or False), then verify by running. Pay attention to comparison operators and remember that Python string comparisons are case-sensitive! This mental exercise helps you debug conditionals faster.

temperature = 25
price = 99.99
name = "Alice"

# Predict the output of each:
print(temperature > 20)
print(price == 100)
print(name == "alice")
print(len(name) >= 5)
Show Solution
temperature = 25
price = 99.99
name = "Alice"

print(temperature > 20)   # True (25 > 20)
print(price == 100)       # False (99.99 is not equal to 100)
print(name == "alice")    # False (case-sensitive: "Alice" != "alice")
print(len(name) >= 5)     # True (len("Alice") is 5, and 5 >= 5)

Task: Given a list of mixed values, write code to print whether each value is truthy or falsy. This exercise helps you internalize which values Python considers "empty" or "nothing" (falsy) versus values with actual content (truthy). Understanding this is crucial for writing elegant Python conditionals.

values = [0, 1, "", "hello", [], [1, 2], None, True, False, -1]
# For each value, print: "value is truthy" or "value is falsy"
Show Solution
values = [0, 1, "", "hello", [], [1, 2], None, True, False, -1]

for value in values:
    if value:
        print(f"{repr(value)} is truthy")
    else:
        print(f"{repr(value)} is falsy")

# Output:
# 0 is falsy
# 1 is truthy
# '' is falsy
# 'hello' is truthy
# [] is falsy
# [1, 2] is truthy
# None is falsy
# True is truthy
# False is falsy
# -1 is truthy

Task: Write a function analyze_number(n) that returns a dictionary with boolean values for: is_positive, is_even, is_two_digit, and is_perfect_square. This combines multiple boolean concepts: comparisons, modulo for even/odd, range checking, and mathematical operations. A perfect square is a number whose square root is an integer (like 4, 9, 16, 25).

Show Solution
import math

def analyze_number(n):
    """Analyze various properties of a number."""
    return {
        "is_positive": n > 0,
        "is_even": n % 2 == 0,
        "is_two_digit": 10 <= abs(n) <= 99,
        "is_perfect_square": n >= 0 and int(math.sqrt(n)) ** 2 == n
    }

# Test the function
print(analyze_number(16))
# {'is_positive': True, 'is_even': True, 'is_two_digit': True, 'is_perfect_square': True}

print(analyze_number(25))
# {'is_positive': True, 'is_even': False, 'is_two_digit': True, 'is_perfect_square': True}

print(analyze_number(-7))
# {'is_positive': False, 'is_even': False, 'is_two_digit': False, 'is_perfect_square': False}
02

if, elif, and else Statements

Now that you understand boolean values, let us learn the actual keywords that make conditionals work: if, elif, and else. These are the building blocks you will use thousands of times in your programming journey!

Key Concept

The Three Conditional Keywords

Python uses three keywords for conditional logic: if starts the decision, elif (else-if) adds more options, and else catches everything remaining. Together, they create a decision tree where exactly one block of code executes based on which condition is true first.

Think of it like this: "If it is raining, take an umbrella. Else if it is sunny, wear sunglasses. Else (for any other weather), just go outside." Python evaluates each condition in order and stops at the first true one!

if

"If this is true, do this thing." Starts every conditional. Required.

elif

"Otherwise, if THIS is true instead..." Short for "else if". Optional, can have many.

else

"If nothing above was true, do this." The catch-all safety net. Optional.

These three keywords work together like a flowchart with decision points. You start by checking the if condition. If it fails, you check each elif condition in order. If they all fail, the else block runs as a catch-all. Only ONE of these blocks will ever run - as soon as Python finds a true condition, it runs that block and skips all the rest.

Python's Unique Feature: Python uses indentation (4 spaces) to define code blocks instead of curly braces {} like other languages. This makes code visually clean but means whitespace is part of the syntax!

The Basic if Statement

Let us start with the simplest conditional: the if statement. It is exactly what it sounds like - "if something is true, do this."

if Statement Structure
  1. Write the word if
  2. Write a condition (True or False)
  3. Put a colon : at the end
  4. Indent the code block with 4 spaces
Syntax Pattern
if condition:
    # This code runs only if
    # condition is True
    do_something()

If the condition is True, Python runs the indented code. If the condition is False, Python skips it completely and moves on. Let us see a real example:

# Basic if statement syntax
age = 20

if age >= 18:
    print("You are an adult")
    print("You can vote in elections")

print("This always prints, regardless of age")

# Output:
# You are an adult
# You can vote in elections
# This always prints, regardless of age
Code Analysis

Step by Step Breakdown

Follow the execution flow of this if statement

1
Set Variable

We assign the value

age = 20
2
Check Condition

Python evaluates:

age >= 18
20 >= 18 = True!
3
Execute Block

Condition is True!

Runs indented code
4
Continue

Outside the if block

Always runs
Mental Exercise: Try changing age to 15 in your mind. The condition 15 >= 18 would be False, so Python would skip the indented block entirely, and only the last line would print!

Notice the colon (:) after the condition and the indentation of the code block. Python uses indentation (typically 4 spaces) to define code blocks, unlike languages that use curly braces. The indented lines belong to the if statement and only execute when the condition is true.

Common Mistake: Forgetting the colon : after the condition is one of the most frequent syntax errors for beginners. Python will raise a SyntaxError if you forget it. Always remember: condition followed by colon, then indented block!

The standard convention in Python is to use 4 spaces for each level of indentation. While you can technically use tabs or a different number of spaces, mixing tabs and spaces will cause errors, and the Python community strongly recommends 4 spaces. Most code editors can be configured to insert 4 spaces when you press the Tab key.

The if-else Statement

Two-Way Decision

What is if-else?

The if-else statement is like standing at a fork in the road. You MUST choose one path - you cannot go both ways or stand still. Based on a condition, Python will execute exactly one of two code blocks - never both, never neither.

Key Insight: Use if-else when you need to handle both outcomes of a decision - what to do when something is true AND what to do when it's false.

Real-World Analogy: Think of if-else like a light switch:
  • If the switch is ON → the light shines
  • Else (switch is OFF) → the room stays dark
There's no middle ground - the light is either on or off. Similarly, if-else guarantees one of two actions will happen!
Syntax Structure
# Basic if-else syntax structure
if condition:
    # This block runs when condition is True
    # Can have multiple lines
    # All lines must be indented
else:
    # This block runs when condition is False
    # Also can have multiple lines
    # Also must be indented
Decision Flow

The Two Paths

Every if-else creates exactly two possible execution paths

Path A
if block
Condition = True
When does this run?

This block executes only when Python evaluates your condition and finds it to be True. The moment the condition passes, Python enters this block.

What happens inside?

All indented code under the if statement runs line by line. You can have as many lines as needed - print statements, calculations, function calls, even nested conditions!

Real-World Example

Scenario: Checking if it's raining

If is_raining == True → Take umbrella, wear raincoat, drive carefully

Path B
else block
Condition = False
When does this run?

This block is the fallback option. It executes when Python checks the if condition and finds it to be False. Think of it as the "otherwise" action.

What happens inside?

Just like the if block, all indented code under else runs sequentially. The else keyword doesn't need a condition - it automatically catches everything the if missed.

Real-World Example

Scenario: Checking if it's raining

Else (not raining) → Leave umbrella home, enjoy the sunshine!

Remember: Only ONE path executes — never both, never neither!
Practical Example
# if-else statement - Weather Decision
temperature = 15

if temperature >= 25:
    print("It is warm outside")
    print("Wear light clothes")
else:
    print("It is cool outside")
    print("Consider wearing a jacket")

# Output (since temperature is 15):
# It is cool outside
# Consider wearing a jacket
Code Analysis

How This Code Works

Follow the execution flow step by step

1
Check Condition

Python reads the if statement and evaluates the condition:

temperature >= 25
Is 15 greater than or equal to 25?
Result: False

15 is NOT >= 25, so condition fails

2
Skip if Block

Since condition is False, Python completely ignores the if block:

print("It is warm outside") print("Wear light clothes")
Skipped!

These lines never execute

3
Run else Block

Python jumps to the else block and executes its code:

print("It is cool outside") print("Consider wearing a jacket")
Executed!

Output prints to console

Final Output: It is cool outside Consider wearing a jacket

The else block catches all cases where the if condition fails. Think of it as a safety net - if the main condition is not met, the program falls through to the else block.

Important Rules to Remember:
  1. The else keyword does NOT have a condition - it automatically catches everything the if missed
  2. The else block is optional - you can use if alone without else
  3. When you include else, exactly one of the two blocks will always execute
  4. Both if and else must end with a colon :
Example: Random Number Decision
# Example showing if-else guarantees one block executes
import random

number = random.randint(1, 10)

if number > 5:
    print(f"{number} is greater than 5")
else:
    print(f"{number} is 5 or less")

# One of these messages will ALWAYS print, never both, never neither
Mental Model for Beginners

Read if-else in plain English: "If this condition is true, do this thing. Otherwise (else), do that other thing." The word "else" in Python means exactly what it means in everyday language - it's the alternative option!

The if-elif-else Chain

When you have multiple conditions to check, use elif (short for "else if"). This lets you create a chain of conditions where Python checks each one in order and executes the first block whose condition is True.

How It Works

elif Chain Execution Flow

Python checks conditions from top to bottom. The moment it finds a True condition, it executes that block and skips all remaining elif and else blocks. This is called "early exit" or "short-circuit evaluation."

Critical Rule: Order your conditions from most specific to least specific. If you put a general condition first, it will catch cases meant for more specific conditions below it!

Grade Classification Example

Breaking down an if-elif-else chain step by step

# if-elif-else chain for grade classification
score = 78

We create a variable called score and assign it the value 78. This is the test score we want to convert into a letter grade.

if score >= 90:
    grade = "A"
    message = "Excellent work!"

The if statement checks if score is greater than or equal to 90. Since 78 >= 90 is False, this block is skipped and Python moves to the next elif.

elif score >= 80:
    grade = "B"
    message = "Good job!"

The first elif checks if score is greater than or equal to 80. Since 78 >= 80 is False, this block is also skipped and Python continues to the next condition.

elif score >= 70:
    grade = "C"
    message = "Satisfactory"

This elif checks if score is greater than or equal to 70. Since 78 >= 70 is True, this block executes! The variables grade and message are assigned. After this, Python exits the entire chain and skips all remaining conditions.

elif score >= 60:
    grade = "D"
    message = "Needs improvement"
else:
    grade = "F"
    message = "Please see instructor"

These blocks are never checked because Python already found a matching condition above (score >= 70). Even though 78 >= 60 is also True, this elif is never evaluated. The else block serves as a catch-all for any score below 60.

print(f"Score: {score}")
print(f"Grade: {grade}")
print(f"Feedback: {message}")

These print() statements display the final results using f-strings. The variables contain: score = 78, grade = "C", and message = "Satisfactory".

Console Output
What you see when you run this code
Score: 78 Grade: C Feedback: Satisfactory

In this example, even though score >= 70 and score >= 60 are both true (78 satisfies both), only the first matching condition executes. Once Python finds a true condition, it skips all remaining elif and else blocks.

Common Mistake: Wrong Order

The order of your conditions matters enormously! Here is a comparison:

Wrong Order (Bug!)
score = 95

if score >= 60:    # True for 95!
    grade = "D"    # WRONG!
elif score >= 70:
    grade = "C"
elif score >= 80:
    grade = "B"
elif score >= 90:
    grade = "A"    # Never reached!

# Result: grade = "D" (Wrong!)
Correct Order
score = 95

if score >= 90:    # Most specific first
    grade = "A"    # Catches 90+
elif score >= 80:
    grade = "B"    # Catches 80-89
elif score >= 70:
    grade = "C"    # Catches 70-79
elif score >= 60:
    grade = "D"    # Catches 60-69

# Result: grade = "A" (Correct!)
Golden Rule: In elif chains, always put more specific conditions first. Order from highest/most restrictive to lowest/least restrictive!

Multiple Independent Conditions

Sometimes you need to check conditions that are independent of each other - where multiple conditions could all be true and you want to act on each one. In this case, use separate if statements instead of elif.

Key Difference: Use elif when conditions are mutually exclusive (only one should run). Use multiple ifs when conditions are independent (several might run).
# Multiple independent conditions (not elif!)
user_age = 25
user_income = 60000
has_good_credit = True

We set up three variables to represent a loan applicant's profile. Each variable stores different information: age (25 years), annual income ($60,000), and credit status (good credit). These will be checked independently.

print("Loan eligibility check:")

This prints a header message to introduce what the program is doing. It helps make the output clear and organized for the user.

# Each condition is checked independently
if user_age >= 18:
    print("- Age requirement: PASSED")

The first if statement checks if the user is at least 18 years old. Since user_age is 25, this condition is True and the message prints. Notice this is a standalone if, not connected to the others.

if user_income >= 50000:
    print("- Income requirement: PASSED")

The second if statement checks if income is at least $50,000. This is a completely separate check from the age check. Both can pass, both can fail, or any combination. Since income is $60,000, this also passes.

if has_good_credit:
    print("- Credit requirement: PASSED")

The third if checks the credit status. Since has_good_credit is already a boolean (True), we don't need to compare it to anything. Python evaluates it directly. This also passes.

# All three conditions are checked separately
# Output:
# Loan eligibility check:
# - Age requirement: PASSED
# - Income requirement: PASSED
# - Credit requirement: PASSED

The key point: all three messages printed because each if statement runs independently. If we had used elif instead, only the first passing condition would have printed. Use multiple ifs when you want to check and act on multiple conditions separately.

Unlike an elif chain where only one block executes, here each if statement is evaluated independently. This is useful when you need to check multiple unrelated conditions or perform multiple actions.

Quick Reference: Conditional Structures

Structure Use Case Blocks Executed
if only Optional action when condition is true 0 or 1
if-else Two mutually exclusive options Exactly 1
if-elif-else Multiple mutually exclusive options Exactly 1
Multiple ifs Independent conditions 0 to all

Practice: if-elif-else

Task: Write a program that takes an age and prints whether the person can vote (18 or older), will be able to vote soon (16-17 years old), or is too young (under 16). This exercise reinforces the if-elif-else chain pattern with age-based ranges. Consider what message would be most helpful for each category.

Show Solution
age = 17

if age >= 18:
    print("You are eligible to vote!")
elif age >= 16:
    print(f"You will be able to vote in {18 - age} year(s)")
else:
    print("You are too young to vote")
    print(f"You need to wait {18 - age} more years")

# Output for age = 17:
# You will be able to vote in 1 year(s)

Task: Create a ticket pricing system where: Children (0-12) pay Rs. 100, Teens (13-17) pay Rs. 200, Adults (18-59) pay Rs. 300, Seniors (60+) pay Rs. 150. This exercise demonstrates that elif chains can check overlapping ranges by testing from most specific to least specific. Order your conditions carefully!

Show Solution
age = 45

if age < 0:
    print("Invalid age!")
elif age <= 12:
    price = 100
    category = "Child"
elif age <= 17:
    price = 200
    category = "Teen"
elif age <= 59:
    price = 300
    category = "Adult"
else:
    price = 150
    category = "Senior"

if age >= 0:
    print(f"Category: {category}")
    print(f"Ticket Price: Rs. {price}")

# Output for age = 45:
# Category: Adult
# Ticket Price: Rs. 300

Task: Calculate BMI (weight in kg / height in m squared) and classify: Underweight (below 18.5), Normal (18.5-24.9), Overweight (25-29.9), Obese (30+). Include personalized health advice for each category.

Show Solution
weight = 70  # kg
height = 1.75  # meters

bmi = weight / (height ** 2)

print(f"Your BMI: {bmi:.1f}")

if bmi < 18.5:
    category = "Underweight"
    advice = "Consider consulting a nutritionist for a healthy weight gain plan."
elif bmi < 25:
    category = "Normal weight"
    advice = "Great job! Maintain your healthy lifestyle."
elif bmi < 30:
    category = "Overweight"
    advice = "Consider increasing physical activity and reviewing your diet."
else:
    category = "Obese"
    advice = "Please consult a healthcare provider for personalized guidance."

print(f"Category: {category}")
print(f"Advice: {advice}")

# Output for weight=70, height=1.75:
# Your BMI: 22.9
# Category: Normal weight
# Advice: Great job! Maintain your healthy lifestyle.
03

Comparison and Logical Operators

Building Blocks

The Tools for Building Conditions

Conditions in Python are built using two types of operators: comparison operators (to compare values) and logical operators (to combine multiple conditions). These operators always produce a True or False result.

Comparison Operators

Compare two values and ask questions like:

  • "Is this bigger?"
  • "Are these equal?"
  • "Is this less than that?"
Logical Operators

Combine multiple conditions and ask:

  • "Is this true AND that true?"
  • "Is this true OR that true?"
  • "Is this NOT true?"
Detective Analogy: Think of it like being a detective. Comparison operators help you gather evidence ("Is the suspect over 6 feet tall? True."). Logical operators help you combine evidence ("Is the suspect over 6 feet tall AND have brown hair AND own a red car?"). Together, they let you express any question you can think of!

Comparison Operators

Comparison operators are symbols used to compare two values. They ask simple yes-or-no questions about your data, and Python answers with True or False.

==
Equal To

Checks if two values are exactly the same. Works with numbers, strings, lists, and more.

5 == 5 → True
"hi" == "hi" → True
!=
Not Equal To

Checks if two values are different. Returns True when values don't match.

5 != 3 → True
"a" != "b" → True
>
Greater Than

Checks if the left value is strictly larger than the right value.

10 > 5 → True
5 > 5 → False
<
Less Than

Checks if the left value is strictly smaller than the right value.

3 < 10 → True
5 < 5 → False
>=
Greater Than or Equal

Checks if left is larger OR equal. Useful for "at least" conditions.

5 >= 5 → True
age >= 18 (voting)
<=
Less Than or Equal

Checks if left is smaller OR equal. Useful for "at most" conditions.

5 <= 5 → True
items <= 10 (limit)
When to Use Each Operator:
  • == — Checking passwords, comparing user input, validating data
  • != — Checking if something changed, excluding values
  • > / < — Comparing scores, prices, quantities
  • >= / <= — Age restrictions, minimum/maximum limits, thresholds
Super Important for Beginners!

The difference between = (single equals) and == (double equals) trips up almost everyone at first!

= (single) = Assign a value
== (double) = Compare values
# Assignment vs Comparison - know the difference!
x = 5       # Assignment: x now holds the value 5
x == 5      # Comparison: checks if x equals 5, returns True
x == 10     # Comparison: checks if x equals 10, returns False

# Common mistake in conditionals:
# if x = 5:   # WRONG! This is assignment, causes SyntaxError
if x == 5:    # CORRECT! This is comparison
    print("x is five")

The single equals = stores a value in a variable, while double equals == compares two values and returns True or False.

Operator Name Example Result
==Equal to5 == 5True
!=Not equal to5 != 3True
>Greater than5 > 3True
<Less than5 < 3False
>=Greater than or equal5 >= 5True
<=Less than or equal5 <= 3False
# Comparison operators in action
x = 10
y = 5

print(f"x = {x}, y = {y}")
print(f"x == y: {x == y}")   # False (10 is not equal to 5)
print(f"x != y: {x != y}")   # True (10 is not equal to 5)
print(f"x > y: {x > y}")     # True (10 is greater than 5)
print(f"x < y: {x < y}")     # False (10 is not less than 5)
print(f"x >= y: {x >= y}")   # True (10 is greater than or equal to 5)
print(f"x <= y: {x <= y}")   # False (10 is not less than or equal to 5)

Each comparison returns a boolean value (True or False) that can be used directly in conditionals or stored in variables.

Comparing Strings

Text Comparison

How String Comparison Works in Python

Strings are compared character by character based on their Unicode values (like alphabetical order, but for all characters). Python compares the first characters, then the second, and so on until it finds a difference.

Case Sensitivity: "Apple" and "apple" are NOT equal! Uppercase letters have different Unicode values than lowercase letters.

Equality
"hello" == "hello" → True "Hello" == "hello" → False

Must match exactly, including case

Alphabetical Order
"apple" < "banana" → True "zebra" > "apple" → True

"a" comes before "b" in alphabet

Case Order
"A" < "a" → True "Z" < "a" → True

All uppercase before lowercase

# String comparison
print("apple" == "apple")    # True (exact match)
print("apple" == "Apple")    # False (case-sensitive!)
print("apple" < "banana")    # True (a comes before b)
print("Apple" < "apple")     # True (uppercase A has lower Unicode value)

# Common use case: case-insensitive comparison
name1 = "Alice"
name2 = "alice"
print(name1.lower() == name2.lower())  # True (convert both to lowercase first)

To compare strings without worrying about case, use .lower() or .upper() to convert both strings to the same case before comparing.

Practical Tips for String Comparison:
  • .lower() or .upper() — Use for case-insensitive comparison
  • .strip() — Remove whitespace before comparing user input
  • in operator — Check if substring exists (e.g., "or" in "World")
  • .startswith() / .endswith() — Check beginning or end of strings

Logical Operators

Logical operators combine multiple conditions into one. Python has three logical operators:

and
Both Must Pass

Returns True only if BOTH conditions are true. If either one is false, the whole expression is false.

True and TrueTrue
True and FalseFalse
False and TrueFalse
False and FalseFalse

Example: age >= 18 and has_id — Must be adult AND have ID

or
Either Can Pass

Returns True if AT LEAST ONE condition is true. Only false if both are false.

True or TrueTrue
True or FalseTrue
False or TrueTrue
False or FalseFalse

Example: is_admin or is_owner — Either role has access

not
Flip the Answer

Inverts a boolean value. Turns True to False and vice versa. Only takes ONE operand.

not TrueFalse
not FalseTrue
not (5 > 3)False
not (5 < 3)True

Example: not is_banned — Allow if NOT banned

Practical Applications

Real-World Examples of Logical Operators

Login System (and)
username_correct and password_correct

Both must match to allow login

Discounts (or)
is_member or has_coupon

Either condition gives discount

Moderation (not)
not is_spam

Show comment if NOT spam

Operator Description Example Result
andBoth must be TrueTrue and FalseFalse
orAt least one must be TrueTrue or FalseTrue
notInverts the booleannot TrueFalse
# Logical operators
age = 25
has_license = True
has_insurance = False

We create three variables to represent a person's profile. age stores a number (25), while has_license and has_insurance store boolean values (True/False). These will be used to demonstrate how logical operators work.

# and: both conditions must be True
can_rent_car = age >= 21 and has_license
print(f"Can rent car: {can_rent_car}")  # True (25 >= 21 AND has license)

The and operator requires BOTH conditions to be True. Here we check if age is at least 21 (True, because 25 >= 21) AND if the person has a license (True). Since both are True, can_rent_car becomes True.

# or: at least one condition must be True
needs_attention = age < 18 or age > 65
print(f"Needs attention: {needs_attention}")  # False (neither condition is True)

The or operator requires AT LEAST ONE condition to be True. We check if age is under 18 (False) OR over 65 (False). Since both are False, needs_attention is False. If either condition were True, the result would be True.

# not: inverts the boolean value
is_uninsured = not has_insurance
print(f"Is uninsured: {is_uninsured}")  # True (not False = True)

The not operator flips a boolean value. Since has_insurance is False, not has_insurance becomes True. This is useful when you want to check the opposite of a condition.

# Combining multiple operators
is_eligible = (age >= 21) and has_license and (not has_insurance)
print(f"Eligible for special offer: {is_eligible}")  # True

You can combine multiple logical operators in one expression. This checks three things: age >= 21 (True), has_license (True), and not has_insurance (True). Since all three are True, the result is True. Parentheses make the expression easier to read.

Performance Feature

Short-Circuit Evaluation

Python is smart! It stops evaluating as soon as it knows the final result:

and — If first is False, skip the rest (result is already False)
or — If first is True, skip the rest (result is already True)
# Safe division using short-circuit evaluation
x = 10
y = 0

# The second condition only runs if y != 0
if y != 0 and x / y > 5:
    print("Result is greater than 5")

# Without short-circuit, x / y would cause ZeroDivisionError!

Short-circuit evaluation is useful for safety checks — the division only happens if y is not zero.

Chained Comparisons

Python Feature

What are Chained Comparisons?

Python allows you to chain comparison operators together, just like in mathematics! Instead of writing x > 0 and x < 10, you can write 0 < x < 10. This is more readable and closer to how we think naturally.

Bonus: Chained comparisons are also more efficient — Python evaluates each value only once, which matters when values come from expensive function calls.

Traditional Way (Other Languages)
# Verbose and repetitive
if score >= 70 and score < 80:
    grade = "C"

# Check if x is between 1 and 10
if x > 1 and x < 10:
    print("In range")
Python's Chained Way (Better!)
# Clean and mathematical
if 70 <= score < 80:
    grade = "C"

# Check if x is between 1 and 10
if 1 < x < 10:
    print("In range")
# Chained comparisons - Python allows this!
score = 75

# Instead of: score >= 70 and score < 80
if 70 <= score < 80:
    print("Grade: C")

# Multiple chained comparisons
x = 5
print(1 < x < 10)     # True (x is between 1 and 10)
print(1 < x < 3)      # False (x is not between 1 and 3)
print(0 < x == 5)     # True (x > 0 AND x equals 5)

# Real-world example: time validation
hour = 14
if 9 <= hour < 17:
    print("Business hours")  # This prints for hour = 14
else:
    print("Closed")

Chained comparisons make range checks intuitive. 70 <= score < 80 reads as "score is at least 70 and less than 80."

Common Use Cases for Chained Comparisons:
  • 0 <= percentage <= 100 — Validating percentage is within valid range
  • 18 <= age < 65 — Checking working age population
  • 9 <= hour < 17 — Business hours check
  • 'a' <= char <= 'z' — Checking if character is lowercase letter

Identity and Membership Operators

Object Identity

Identity Operators

Check if two variables point to the exact same object in memory, not just equal values.

is

Same object?

is not

Different object?

Collection Search

Membership Operators

Check if a value exists within a collection like lists, strings, tuples, or dictionaries.

in

Is inside?

not in

Not inside?

Understanding == vs is:

Think of identical twins. They look the same (equal values), but they are different people (different objects). == checks if they look the same, is checks if they are literally the same person.

a = [1, 2, 3]
b = [1, 2, 3]
a == bTrue (same values)
a is bFalse (different objects)
a = [1, 2, 3]
c = a (c points to same list)
a == cTrue (same values)
a is cTrue (same object!)
# Identity operators (check if same object in memory)
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)       # True (same values)
print(a is b)       # False (different objects in memory)
print(a is c)       # True (c points to same object as a)

# None should always be checked with 'is'
result = None
if result is None:
    print("No result yet")

The is operator is mainly used for checking None. For most comparisons, use ==.

Membership Operators

Where Can You Use in?

Lists
"a" in [1, "a", 3]
Strings
"or" in "World"
Dictionaries
"key" in my_dict
Tuples/Sets
5 in (1, 5, 10)
# Membership operators
fruits = ["apple", "banana", "cherry"]
print("apple" in fruits)     # True
print("mango" not in fruits)  # True

# Works with strings too
message = "Hello, World!"
if "World" in message:
    print("Found 'World' in the message")

# Check dictionary keys
user = {"name": "Alice", "age": 25}
if "email" not in user:
    print("Email not provided")  # This prints

# Useful for input validation
valid_choices = ["yes", "no", "maybe"]
user_input = "yes"
if user_input.lower() in valid_choices:
    print(f"Valid choice: {user_input}")

The in operator is perfect for checking if user input is valid, if a key exists in a dictionary, or if a substring is in a larger string.

Important: Use is only for comparing with None, True, or False. For comparing values, always use ==. Using is for value comparison can lead to unexpected bugs!
Operator Type Description Example Result
isIdentitySame object in memoryx is NoneDepends
is notIdentityDifferent objectsx is not NoneDepends
inMembershipValue exists in collection"a" in ["a", "b"]True
not inMembershipValue not in collection"c" not in ["a", "b"]True

Practice: Operators

Task: Write code to check if a percentage score is valid (between 0 and 100 inclusive) using chained comparison. Python's chained comparisons like 0 <= score <= 100 make range validation elegant and readable. This is much cleaner than score >= 0 and score <= 100.

Show Solution
score = 85

if 0 <= score <= 100:
    print(f"Valid score: {score}%")
else:
    print(f"Invalid score: {score}. Must be between 0-100")

# Output: Valid score: 85%

# Test with invalid score
score = 150
if 0 <= score <= 100:
    print(f"Valid score: {score}%")
else:
    print(f"Invalid score: {score}. Must be between 0-100")

# Output: Invalid score: 150. Must be between 0-100

Task: Create a login system that checks: username must be at least 4 characters, password must be at least 8 characters, and they cannot be the same (case-insensitive). Use logical operators to combine checks. Provide specific error messages for each failed validation so users know exactly what to fix.

Show Solution
username = "alice"
password = "securePass123"

# Individual checks
valid_username = len(username) >= 4
valid_password = len(password) >= 8
not_same = username.lower() != password.lower()

# Combined validation
if valid_username and valid_password and not_same:
    print("Login credentials are valid!")
else:
    print("Invalid credentials:")
    if not valid_username:
        print("- Username must be at least 4 characters")
    if not valid_password:
        print("- Password must be at least 8 characters")
    if not not_same:
        print("- Username and password cannot be the same")

# Output: Login credentials are valid!

Task: Create an access control system where: admins can access everything (settings, users, reports, logs, database), managers can access reports and users only, regular users can only view their own data. This models real-world role-based access control (RBAC) and demonstrates combining membership operators with logical operators.

Show Solution
user_role = "manager"
requested_resource = "reports"
user_id = "user123"
resource_owner = "user456"

# Define access permissions
admin_resources = ["settings", "users", "reports", "logs", "database"]
manager_resources = ["users", "reports"]
user_resources = ["own_profile", "own_data"]

def check_access(role, resource, user_id, owner_id):
    # Admins can access everything
    if role == "admin":
        return True
    
    # Managers can access specific resources
    if role == "manager" and resource in manager_resources:
        return True
    
    # Users can only access their own data
    if role == "user":
        is_own_resource = resource in user_resources
        is_owner = user_id == owner_id
        if is_own_resource or is_owner:
            return True
    
    return False

# Test access
has_access = check_access(user_role, requested_resource, user_id, resource_owner)
print(f"Role: {user_role}")
print(f"Requesting: {requested_resource}")
print(f"Access granted: {has_access}")

# Output:
# Role: manager
# Requesting: reports
# Access granted: True
04

Nested Conditionals

Control Flow Concept

What are Nested Conditionals?

Sometimes you need to make a decision that depends on a previous decision. Nested conditionals are simply an if statement inside another if statement! Each level of nesting represents a deeper decision that only makes sense if the previous condition was True.

Think of it like: A choose-your-own-adventure book. "If you have a sword, go to page 10. On page 10, if the dragon is sleeping, sneak past. If the dragon is awake, prepare to fight." The second choice only makes sense if you already have the sword!

Decision Tree

Each level of nesting takes you deeper into your decision tree. Inner conditions only run if outer conditions pass.

Pyramid of Doom

Too much nesting (4+ levels) creates hard-to-read code. Look for ways to "flatten" when possible.

When to Use

Use when decisions are truly dependent - the inner check only makes sense if the outer check passed first.

Basic Nested Structure

Real-World Example

ATM Withdrawal System

Imagine you are programming an ATM machine. You cannot just give people money right away - you need to check several things in order. Each check only makes sense if the previous one passed.

1
Card Inserted?

First, check if they inserted a card

2
PIN Correct?

Only THEN check the PIN

3
Enough Balance?

Only THEN check the balance

4
Dispense Cash!

Only THEN give the cash

# Nested conditional example - ATM withdrawal
has_card = True
correct_pin = True
balance = 5000
withdrawal_amount = 3000

We set up four variables to simulate an ATM transaction: a card is inserted, the PIN is correct, the account has Rs. 5000, and the user wants to withdraw Rs. 3000.

if has_card:
    print("Card detected")

The outermost if checks if a card is inserted. Only when this is True do we proceed to check anything else. This is the first "gate" in our security chain.

    if correct_pin:
        print("PIN verified")

Inside the first if, we check the PIN. This is a nested if - it only runs if the card check passed. Notice the extra indentation showing it's inside the first block.

        if withdrawal_amount <= balance:
            balance -= withdrawal_amount
            print(f"Dispensing Rs. {withdrawal_amount}")
            print(f"New balance: Rs. {balance}")

The deepest nested if checks if there's enough money. If all three conditions pass, we subtract the amount from the balance and dispense the cash. This is the "happy path" - everything went right!

        else:
            print("Insufficient funds!")
    else:
        print("Incorrect PIN!")
else:
    print("Please insert card")

Each else handles the failure case at its level. The error messages are specific to which check failed: no card, wrong PIN, or not enough money. The indentation matches the if it belongs to.

Tracing the Logic:
  1. Outer check: Does the user have a card? ✓ Yes, so we continue inside.
  2. First nested: Is the PIN correct? ✓ Yes, so we continue deeper.
  3. Second nested: Is 3000 <= 5000? ✓ Yes, so we dispense the cash!

Each level of nesting depends on the previous check passing. It would not make sense to check the balance before verifying the PIN, or to dispense money before checking the balance. This dependency chain is what makes nested conditionals appropriate here.

Flattening Nested Conditions

Pro Technique

Early Returns & Guard Clauses

Instead of nesting deeper and deeper, you can "flatten" your code using early returns or guard clauses. Check for all the "bad" cases at the start and exit early if any are true. This leaves the "happy path" (successful case) at the end.

Compare these two approaches - they do the exact same thing, but one is much easier to read:

Deep Nesting (Harder to Read)
# Deep nesting (harder to read)
def process_order_nested(order):

We define a function called process_order_nested that takes an order dictionary as input. This function needs to verify several things before processing the order: the order must exist, have a pending status, contain items, and have verified payment. The nested approach checks these conditions by going deeper and deeper into indentation levels.

    if order is not None:

First level of nesting: We check if the order object actually exists (is not None). If someone calls this function without an order, we don't want the code to crash. Notice that ALL the remaining logic will be indented inside this check — if the order is None, we skip everything and fall through to the final return False.

        if order["status"] == "pending":

Second level of nesting: Inside the first check, we verify the order status is "pending". Orders that are already "shipped", "cancelled", or "completed" shouldn't be processed again. Notice we're now 2 indentation levels deep (8 spaces). The code is starting to drift rightward — this is the beginning of the "pyramid" shape.

            if order["items"]:

Third level of nesting: We check if the order actually contains any items. An empty list [] is "falsy" in Python, so if order["items"]: returns False for empty orders. We're now 3 indentation levels deep (12 spaces). The code is visibly forming a pyramid shape, making it harder to track which if each else would belong to.

                if order["payment_verified"]:
                    print("Processing order...")
                    return True
    return False

Fourth level of nesting: Finally, at 4 levels deep (16 spaces of indentation!), we check if payment was verified. Only after passing ALL four nested checks do we actually process the order and return True. The lonely return False at the bottom catches ANY failure from ANY level — but which check failed? We have no idea without debugging. This "pyramid of doom" is confusing, error-prone, and difficult to maintain.

Flattened Version (Cleaner!)
# Flattened version using early returns (cleaner!)
def process_order_flat(order):

This is the exact same logic, rewritten using the guard clause pattern. The key idea: instead of nesting successful cases, we check for failure cases and exit immediately using return. This eliminates nesting entirely! Each check is independent, and the code stays flat at one indentation level.

    if order is None:
        return False

Guard clause 1: If the order doesn't exist, we immediately return False and exit the function. The key difference from nesting: we check for the failure condition (is None) instead of the success condition (is not None). Once this check passes, we KNOW the order exists for all remaining code — we can mentally "forget" about this possibility.

    if order["status"] != "pending":
        return False

Guard clause 2: If the status is NOT pending, exit immediately. Notice the logic is inverted: we use != instead of ==. We're asking "is this condition BAD?" instead of "is this condition good?". If the status is wrong, we bail out. If we get past this line, we KNOW the status is pending — no need to track nested state.

    if not order["items"]:
        return False

Guard clause 3: If there are no items in the order (empty list), exit. The not keyword inverts the truthiness check. Each guard clause reads like a simple rule: "If X is wrong, stop." There's no hunting through nested braces to understand the logic. By this point, we KNOW: order exists, status is pending, and there are items.

    if not order["payment_verified"]:
        return False

Guard clause 4: The final check — if payment isn't verified, exit. We've now handled ALL possible failure cases at the same indentation level. Each guard clause is like a bouncer at a club: "No ID? You're out. Wrong dress code? You're out." Only the valid cases make it through the gauntlet to the next line.

    print("Processing order...")
    return True

The "happy path"! If the code reaches this point, we know for certain: the order exists, status is pending, there are items, and payment is verified. All four guards passed! The main business logic sits at the end, completely unindented from any conditional blocks. This is the beauty of guard clauses — the important code is easy to find, and you don't need to mentally track multiple nested conditions to understand when it runs.

Why Use Guard Clauses?

Benefits of Flattening Code

Reduced Cognitive Load

Handle edge cases first, then forget about them

Flatter Code

Less indentation makes code easier to scan

Clearer Error Handling

Each guard handles one specific failure case

Easier Testing

Each guard clause can be tested independently

When to Use Nested vs Flat

Use nested conditionals when the logic truly requires hierarchical decision-making. Use flat conditionals with logical operators when you are just checking multiple requirements for the same outcome.

# Using logical operators instead of nesting
age = 25
has_license = True
has_insurance = True

# Nested (unnecessary complexity)
if age >= 18:
    if has_license:
        if has_insurance:
            print("You can drive")

# Flattened with 'and' (better!)
if age >= 18 and has_license and has_insurance:
    print("You can drive")

# With meaningful variable for even better readability
meets_driving_requirements = age >= 18 and has_license and has_insurance
if meets_driving_requirements:
    print("You can drive")
Rule of Thumb: If your code has more than 3 levels of nesting, consider refactoring. Extract logic into functions, use early returns, or combine conditions with logical operators.

Practice: Nested Conditionals

Task: Calculate shipping cost based on: domestic/international, weight (light below 2kg, heavy 2kg or more), and express/standard. Use the pricing structure: Domestic standard light: Rs.50, Domestic standard heavy: Rs.100, Domestic express: double standard prices, International: 3x domestic prices. This demonstrates when nested conditionals naturally model hierarchical decisions.

Show Solution
is_international = False
weight = 1.5  # kg
is_express = True

# Calculate base shipping cost
if is_international:
    if weight < 2:
        base_cost = 50 * 3  # 150
    else:
        base_cost = 100 * 3  # 300
else:  # domestic
    if weight < 2:
        base_cost = 50
    else:
        base_cost = 100

# Apply express multiplier
if is_express:
    final_cost = base_cost * 2
else:
    final_cost = base_cost

# Output
shipping_type = "International" if is_international else "Domestic"
weight_cat = "Heavy" if weight >= 2 else "Light"
speed = "Express" if is_express else "Standard"

print(f"Shipping: {shipping_type} {speed} ({weight_cat})")
print(f"Cost: Rs. {final_cost}")

# Output:
# Shipping: Domestic Express (Light)
# Cost: Rs. 100

Task: Refactor the deeply nested function below to use early returns and guard clauses. The goal is to flatten the code by handling failure cases first (returning early), leaving the success path at the end without deep indentation. This is a key professional programming skill.

# Original nested code
def validate_user(user):
    if user is not None:
        if user.get("active"):
            if user.get("email"):
                if "@" in user["email"]:
                    if user.get("age", 0) >= 18:
                        return "User is valid"
    return "User is invalid"
Show Solution
def validate_user(user):
    """Validate user with guard clauses - much cleaner!"""
    
    # Guard clause: check if user exists
    if user is None:
        return "User is invalid: No user provided"
    
    # Guard clause: check if user is active
    if not user.get("active"):
        return "User is invalid: Account not active"
    
    # Guard clause: check if email exists
    email = user.get("email")
    if not email:
        return "User is invalid: No email provided"
    
    # Guard clause: check email format
    if "@" not in email:
        return "User is invalid: Invalid email format"
    
    # Guard clause: check age
    if user.get("age", 0) < 18:
        return "User is invalid: Must be 18 or older"
    
    # All checks passed!
    return "User is valid"

# Test the refactored function
test_user = {
    "active": True,
    "email": "alice@example.com",
    "age": 25
}
print(validate_user(test_user))  # User is valid

invalid_user = {"active": True, "email": "no-at-sign"}
print(validate_user(invalid_user))  # User is invalid: Invalid email format
05

Ternary (Conditional) Operator

One-Line Conditional

What is the Ternary Operator?

Sometimes writing a full if-else block feels like too much work for a simple decision. Python has a shortcut called the ternary operator (also called a "conditional expression") that lets you write a simple if-else in just one line!

Why "Ternary"? The name just means "three parts" — and that's exactly what this operator has: the true value, the condition, and the false value.

1
Value if True
"adult"

What you get when condition passes

2
The Condition
if age >= 18

The test that returns True/False

3
Value if False
else "minor"

What you get when condition fails

The Complete Syntax
value_if_true if condition else value_if_false

Reads like English: "give me 'adult' if age is 18 or more, otherwise give me 'minor'"

Beginner Tip: The ternary operator is great for simple choices between two values. But if your logic is complicated, stick with regular if-else blocks — clarity is more important than saving lines of code!

Basic Syntax

Let us compare a regular if-else with its ternary equivalent:

# Traditional if-else
age = 20
if age >= 18:
    status = "adult"
else:
    status = "minor"

# Same thing with ternary operator (one line!)
status = "adult" if age >= 18 else "minor"
print(status)  # adult

# Using ternary in print statements
score = 85
print(f"Result: {'Pass' if score >= 40 else 'Fail'}")  # Result: Pass

# Assigning different values
temperature = 35
message = "Hot day!" if temperature > 30 else "Pleasant weather"
print(message)  # Hot day!

Common Use Cases

The ternary operator shines in situations where you need to quickly choose between two values. It keeps your code concise without sacrificing clarity.

# Use case 1: Setting default values
user_name = ""
display_name = user_name if user_name else "Guest"
print(f"Welcome, {display_name}")  # Welcome, Guest

# Use case 2: Pluralization
item_count = 5
message = f"You have {item_count} item{'s' if item_count != 1 else ''}"
print(message)  # You have 5 items

# Use case 3: Formatting output
balance = -500
formatted = f"Rs. {balance}" if balance >= 0 else f"Rs. ({abs(balance)})"
print(formatted)  # Rs. (500)

# Use case 4: Boolean conversion with meaning
is_active = True
status_text = "Active" if is_active else "Inactive"
print(status_text)  # Active
Best Practices

When the Ternary Operator Shines

Default Values

When a variable might be empty or None, provide a fallback value instantly.

Pluralization

Dynamically add "s" to words based on count (1 item vs 2 items).

Formatting

Display negative numbers differently, like wrapping in parentheses for accounting.

Human-Readable Conversion

Convert boolean flags (True/False) to descriptive text.

Key Advantage: Each ternary expression produces a value that is immediately used — assigned to a variable or embedded in a string. This is what makes it more elegant than if-else statements in these situations.

Chained Ternary (Use Sparingly!)

Warning for Beginners!

You CAN chain multiple ternary operators together, but just because you can does not mean you should! Chained ternaries quickly become unreadable. Look at this example and try to understand what it does:

# Chained ternary (can be hard to read)
score = 75

We set a test score of 75. We want to convert this number into a letter grade (A, B, C, D, or F).

grade = "A" if score >= 90 else "B" if score >= 80 else "C" if score >= 70 else "D" if score >= 60 else "F"
print(grade)  # C

This is a chained ternary - multiple ternary operators connected together. It checks: if score >= 90 return "A", else if score >= 80 return "B", else if score >= 70 return "C", and so on. Since 75 >= 70, it returns "C". But notice how hard this is to read!

# The same logic is clearer as if-elif-else
if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
elif score >= 60:
    grade = "D"
else:
    grade = "F"

This is the exact same logic written as if-elif-else. It does the same thing but is MUCH easier to read and understand. Each condition is on its own line, and the structure is clear.

# Better alternative: use a function for complex logic
def get_grade(score):
    if score >= 90: return "A"
    if score >= 80: return "B"
    if score >= 70: return "C"
    if score >= 60: return "D"
    return "F"

grade = get_grade(75)  # C

Even better: put the logic in a function! This uses early returns (each return exits the function immediately). The function is reusable and the code is clean. This is what experienced programmers prefer.

Best Practice: Use ternary operators for simple, single conditions. If the logic requires multiple conditions or becomes hard to read, use traditional if-else statements. Readability trumps brevity!

Practice: Ternary Operator

Task: Convert this if-else to a single line ternary expression. The modulo operator % returns the remainder of division, so number % 2 == 0 checks if a number is even. This is a perfect use case for the ternary operator because we are simply choosing between two strings.

number = 7
if number % 2 == 0:
    parity = "even"
else:
    parity = "odd"
Show Solution
number = 7
parity = "even" if number % 2 == 0 else "odd"
print(f"{number} is {parity}")  # 7 is odd

Task: Calculate final price with discount: 20% off if total is Rs. 1000 or more, 10% off if total is Rs. 500 or more, no discount otherwise. Use ternary operators to determine the discount percentage. While chained ternaries can be hard to read, this is a borderline acceptable use case since it is still relatively short.

Show Solution
total = 750

# Determine discount percentage using chained ternary
discount_percent = 20 if total >= 1000 else 10 if total >= 500 else 0

# Calculate discount amount and final price
discount_amount = total * (discount_percent / 100)
final_price = total - discount_amount

print(f"Original: Rs. {total}")
print(f"Discount: {discount_percent}% (Rs. {discount_amount})")
print(f"Final Price: Rs. {final_price}")

# Output:
# Original: Rs. 750
# Discount: 10% (Rs. 75.0)
# Final Price: Rs. 675.0
06

Match-Case (Python 3.10+)

Python 3.10+ Feature

What is Match-Case?

Python 3.10 introduced match-case — a more elegant way to write "if this value is A, do this; if it is B, do that; if it is C, do something else". Instead of checking conditions one by one, you describe patterns that your data might match!

Think of it like: "Hey Python, look at this value and tell me which of these shapes it fits!" It's like a more powerful version of if-elif-else.

Version Requirement

This feature only works in Python 3.10 or newer. Check your version by running python --version in your terminal. If you're using an older version, stick with if-elif-else chains.

Match Exact Values

"Is this exactly 'Monday'?"

Match Multiple

"Is this 'Sat' OR 'Sun'?"

Destructure Data

"Pull apart lists/tuples!"

Add Conditions

"Is x > 100?"

Basic Match-Case Syntax

The Structure is Simple:
  1. Start with match followed by the value you want to check
  2. Add multiple case blocks, each with a pattern to match
  3. Use case _: as a "catch-all" default (the underscore matches anything)

Here is a simple example that checks what day of the week it is:

# Basic match-case (Python 3.10+)
def get_day_type(day):

We define a function that takes a day name as a string. This function will categorize days into different types like "Weekend", "Work day", etc.

    match day.lower():

The match keyword starts the pattern matching. We call .lower() to convert the input to lowercase, so "Saturday", "SATURDAY", and "saturday" all work the same way.

        case "saturday" | "sunday":
            return "Weekend - Time to relax!"

The pipe symbol | means "OR" — this case matches if the day is "saturday" OR "sunday". Both weekend days share the same response. This is cleaner than writing two separate cases!

        case "monday":
            return "Start of the work week"

A simple exact match — if the day is exactly "monday", return this specific message. Monday gets its own special (dreaded?) message.

        case "friday":
            return "TGIF! Almost weekend!"

Another exact match for Friday. TGIF = "Thank God It's Friday!" — Friday also deserves its own celebration message.

        case "tuesday" | "wednesday" | "thursday":
            return "Regular work day"

Multiple values with | again — Tuesday, Wednesday, and Thursday are all "regular" work days, so they share one response. Much cleaner than three separate if statements!

        case _:
            return "Invalid day"

The wildcard pattern _ matches ANYTHING not caught by previous cases. This is your "default" or "else" case. If someone passes "xyz" or "banana", this catches it.

print(get_day_type("Saturday"))  # Weekend - Time to relax!
print(get_day_type("Monday"))    # Start of the work week
print(get_day_type("xyz"))       # Invalid day

Testing our function: "Saturday" matches the weekend case (converted to lowercase first), "Monday" matches its exact case, and "xyz" falls through to the wildcard default.

Pattern Matching with Values

Here is where match-case gets really useful! You can add extra conditions (called "guards") using the if keyword. This lets you match ranges of values instead of listing every single one.

For example, instead of writing case 200: case 201: case 202: ... for all successful HTTP codes, you can write case code if 200 <= code < 300: to match ANY number in that range!

Order Matters! Python checks cases from top to bottom and uses the FIRST one that matches. Put specific cases (like exact numbers) before general cases (like ranges).
# Matching HTTP status codes
def describe_http_status(code):

We define a function that takes an HTTP status code (a number like 200, 404, 500) and returns a human-readable description. This is a perfect use case for match-case!

    match code:

We start matching against the code parameter. Python will check each case pattern from top to bottom until it finds one that matches.

        case 200:
            return "OK - Request successful"
        case 201:
            return "Created - Resource created"
        case 400:
            return "Bad Request - Invalid syntax"
        case 401:
            return "Unauthorized - Authentication required"
        case 403:
            return "Forbidden - Access denied"
        case 404:
            return "Not Found - Resource does not exist"
        case 500:
            return "Internal Server Error"

These are literal matches — exact values we want to handle specifically. The most common HTTP codes get their own descriptive messages. Since these are checked first, they'll match before the range patterns below.

        case code if 200 <= code < 300:
            return f"Success ({code})"

This is a guard pattern! The case code part captures the value into a variable called code, and the if 200 <= code < 300 part adds an extra condition. This matches ANY success code (200-299) that wasn't caught by the specific cases above.

        case code if 400 <= code < 500:
            return f"Client Error ({code})"
        case code if 500 <= code < 600:
            return f"Server Error ({code})"

More guard patterns for client errors (400-499) and server errors (500-599). Instead of listing every possible code, we handle entire ranges with a single case each. The captured code variable lets us include the actual number in the message.

        case _:
            return f"Unknown status: {code}"

The wildcard pattern _ catches anything not matched above. This acts as our default case for unusual status codes like 600+ or negative numbers.

print(describe_http_status(200))  # OK - Request successful
print(describe_http_status(204))  # Success (204)
print(describe_http_status(418))  # Client Error (418)

Testing our function: 200 matches the specific case, 204 matches the 200-299 range pattern (since there's no specific case for it), and 418 (the famous "I'm a teapot" code!) matches the 400-499 range.

Literal Matching

case 200: matches exact values

Capture + Guard

case x if x > 0: captures AND conditions

Order Matters

Specific cases before general ranges

Wildcard

case _: catches everything else

Matching Sequences and Structures

Advanced Pattern Matching

What is Destructuring?

This is where match-case becomes really powerful! You can match based on the shape of your data and pull out individual pieces at the same time. When you write case (x, y):, Python puts the first number into x and the second into y.

Destructuring = Breaking apart a structure into pieces. It's like opening a gift box and taking out each item separately!

Exact Match
(0, 0)

Is it the origin?

Partial Match
(0, y)

Is first zero? Capture second

Capture Both
(x, y)

Match any, grab both values

With Guard
(x, y) if x == y

Match + extra condition

# Matching tuple structures - coordinate processing
def describe_point(point):

We define a function that takes a point (a tuple like (3, 5)) and returns a description of where it is on a coordinate plane. This is a classic use case for destructuring patterns!

    match point:

We start matching against the point tuple. Python will check each case pattern to see if the shape and values match.

        case (0, 0):
            return "Origin"

Exact match: This only matches the specific tuple (0, 0) — the origin point where both coordinates are zero. No variables are captured; we're matching literal values.

        case (0, y):
            return f"On Y-axis at y={y}"

Partial match with capture: This matches any tuple where the first element is exactly 0, and captures the second element into variable y. Points like (0, 5) or (0, -3) would match, and we can use y in our response!

        case (x, 0):
            return f"On X-axis at x={x}"

Another partial match: This matches any tuple where the second element is 0, capturing the first element as x. Points like (3, 0) or (-7, 0) lie on the X-axis.

        case (x, y) if x == y:
            return f"On diagonal at ({x}, {y})"

Pattern + Guard: This matches any 2-tuple AND captures both values, BUT the guard if x == y adds an extra condition — it only matches if both coordinates are equal. Points like (4, 4) or (7, 7) lie on the diagonal line.

        case (x, y):
            return f"Point at ({x}, {y})"

General capture: This matches ANY 2-tuple and captures both values. It's the "catch-all" for valid points that didn't match more specific patterns above. This must come AFTER more specific cases!

        case _:
            return "Not a valid point"

Wildcard: If the input isn't a 2-tuple at all (like a string, a 3-tuple, or None), this catches it and returns an error message.

print(describe_point((0, 0)))     # Origin
print(describe_point((0, 5)))     # On Y-axis at y=5
print(describe_point((3, 0)))     # On X-axis at x=3
print(describe_point((4, 4)))     # On diagonal at (4, 4)
print(describe_point((2, 7)))     # Point at (2, 7)

Testing all the different cases: origin matches exact (0, 0), (0, 5) matches Y-axis pattern, (3, 0) matches X-axis pattern, (4, 4) matches diagonal guard, and (2, 7) falls through to the general capture.

Order Matters! Put specific patterns (like exact coordinates (0, 0)) before general ones (like (x, y)). Python checks from top to bottom and uses the first match!

Matching with Guards

Guards (using if) add additional conditions to patterns. The pattern only matches if both the structure matches AND the guard condition is true.

Guards are essential when you need to match based on criteria that cannot be expressed with patterns alone. For example, you cannot write a pattern that matches "any number greater than 50" - you need a guard for that. Guards give you the full power of Python expressions to add conditions to your patterns.

The guard condition is evaluated only if the pattern matches. This means you can safely use variables captured by the pattern in the guard condition:

# Using guards for additional conditions
def categorize_score(score):
    match score:
        case n if n < 0 or n > 100:
            return "Invalid score"
        case n if n >= 90:
            return "A - Excellent"
        case n if n >= 80:
            return "B - Good"
        case n if n >= 70:
            return "C - Average"
        case n if n >= 60:
            return "D - Below Average"
        case _:
            return "F - Fail"

print(categorize_score(95))   # A - Excellent
print(categorize_score(75))   # C - Average
print(categorize_score(-5))   # Invalid score
Version Note: Match-case requires Python 3.10 or higher. If you are using an older version, you will need to use if-elif-else chains instead. Check your Python version with python --version.

Practice: Match-Case

Task: Create a function that processes commands like "quit", "help", "save filename", "load filename", and returns appropriate messages. Split the command into parts and use match-case with sequence patterns to destructure commands like ["save", filename] where filename is captured into a variable.

Show Solution
def process_command(command):
    parts = command.lower().split()
    
    match parts:
        case ["quit"] | ["exit"]:
            return "Exiting program..."
        case ["help"]:
            return "Available commands: quit, help, save , load "
        case ["save", filename]:
            return f"Saving to {filename}..."
        case ["load", filename]:
            return f"Loading from {filename}..."
        case ["save"] | ["load"]:
            return "Error: Please specify a filename"
        case []:
            return "No command entered"
        case _:
            return f"Unknown command: {' '.join(parts)}"

# Test the command processor
print(process_command("help"))           # Available commands: ...
print(process_command("save data.txt"))  # Saving to data.txt...
print(process_command("load"))           # Error: Please specify a filename
print(process_command("foo bar"))        # Unknown command: foo bar

Task: Create a calculator function that takes a tuple like (num1, operator, num2) and returns the result. Handle +, -, *, /, **, and % operators. Use guards to prevent division by zero. This demonstrates destructuring patterns with guards for comprehensive input validation.

Show Solution
def calculate(expression):
    match expression:
        case (a, "+", b):
            return a + b
        case (a, "-", b):
            return a - b
        case (a, "*", b):
            return a * b
        case (a, "/", b) if b != 0:
            return a / b
        case (a, "/", 0):
            return "Error: Division by zero"
        case (a, "**", b):
            return a ** b
        case (a, "%", b) if b != 0:
            return a % b
        case (_, op, _) if op not in ["+", "-", "*", "/", "**", "%"]:
            return f"Error: Unknown operator '{op}'"
        case _:
            return "Error: Invalid expression format"

# Test the calculator
print(calculate((10, "+", 5)))    # 15
print(calculate((10, "-", 3)))    # 7
print(calculate((4, "*", 7)))     # 28
print(calculate((20, "/", 4)))    # 5.0
print(calculate((10, "/", 0)))    # Error: Division by zero
print(calculate((2, "**", 8)))    # 256
print(calculate((10, "&", 5)))    # Error: Unknown operator '&'

Key Takeaways

Decision Making

Conditionals let your program make decisions, executing different code based on whether conditions are true or false. Every interactive program relies on this fundamental capability.

if-elif-else Chain

Use if for single checks, if-else for two options, and if-elif-else for multiple mutually exclusive conditions. Remember: order matters and exactly one block executes.

Comparison Operators

Use ==, !=, >, <, >=, <= to compare values. Remember that == checks equality while = is assignment. Python allows chained comparisons like 0 < x < 10.

Logical Operators

Combine conditions with and (both must be true), or (at least one must be true), not (inverts boolean). These operators use short-circuit evaluation for efficiency.

Ternary Operator

Use value_if_true if condition else value_if_false for simple, single-line conditional assignments. Keep it simple - complex ternaries hurt readability.

Match-Case (3.10+)

Structural pattern matching for elegant handling of multiple cases. Supports literal matching, destructuring, guards, and OR patterns. Far more powerful than switch statements.

Knowledge Check

1 What will be the output of: print(bool([]))?
2 Which operator should you use to check if a value is exactly None?
3 What is the correct syntax for a ternary operator in Python?
4 In an if-elif-else chain, how many blocks will execute?
5 What does 5 < x < 10 check in Python?
6 Which Python version introduced the match-case statement?
Answer all questions to check your score